Version 2.17.0-116.0.dev
Merge commit '48326ee2e2462d32f53bd38c2a3cff4fce89ef99' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e3ef9d..012897a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,21 @@
- `IdbFactory.supportsDatabaseNames` has been deprecated. It will always return
`false`.
+#### `dart:io`
+
+- **Breaking Change** [#47887](https://github.com/dart-lang/sdk/issues/47887):
+ `HttpClient` has a new `connectionFactory` property, which allows socket
+ creation to be customized. Classes that `implement HttpClient` may be broken
+ by this change. Add the following method to your classes to fix them:
+
+ ```dart
+ void set connectionFactory(
+ Future<ConnectionTask<Socket>> Function(
+ Uri url, String? proxyHost, int? proxyPort)?
+ f) =>
+ throw UnsupportedError("connectionFactory not implemented");
+ ```
+
### Tools
#### Dart command line
diff --git a/DEPS b/DEPS
index 20ccf56..dc28abe 100644
--- a/DEPS
+++ b/DEPS
@@ -126,7 +126,7 @@
"json_rpc_2_rev": "7e00f893440a72de0637970325e4ea44bd1e8c8e",
"linter_tag": "1.18.0",
"lints_tag": "f9670df2a66e0ec12eb51554e70c1cbf56c8f5d0",
- "logging_rev": "575781ef196e4fed4fb737e38fb4b73d62727187",
+ "logging_rev": "dfbe88b890c3b4f7bc06da5a7b3b43e9e263b688",
"markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
"markdown_rev": "7479783f0493f6717e1d7ae31cb37d39a91026b2",
"matcher_rev": "07595a7739d47a8315caba5a8e58fb9ae3d81261",
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index ba6a1d6..817937e 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -18,6 +18,7 @@
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/dartdoc.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart' as lsp;
+import 'package:analysis_server/src/lsp/snippets.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
import 'package:analysis_server/src/protocol_server.dart' as server
hide AnalysisError;
@@ -68,50 +69,6 @@
_asMarkup(preferredFormats, content));
}
-/// Builds an LSP snippet string with supplied ranges as tabstops.
-String buildSnippetStringWithTabStops(
- String? text,
- List<int>? offsetLengthPairs,
-) {
- text ??= '';
- offsetLengthPairs ??= const [];
-
- // Snippets syntax is documented in the LSP spec:
- // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax
- //
- // $1, $2, etc. are used for tab stops and ${1:foo} inserts a placeholder of foo.
-
- final output = [];
- var offset = 0;
-
- // When there's only a single tabstop, it should be ${0} as this is treated
- // specially as the final cursor position (if we use 1, the editor will insert
- // a 0 at the end of the string which is not what we expect).
- // When there are multiple, start with ${1} since these are placeholders the
- // user can tab through and the editor-inserted ${0} at the end is expected.
- var tabStopNumber = offsetLengthPairs.length <= 2 ? 0 : 1;
-
- for (var i = 0; i < offsetLengthPairs.length; i += 2) {
- final pairOffset = offsetLengthPairs[i];
- final pairLength = offsetLengthPairs[i + 1];
-
- // Add any text that came before this tabstop to the result.
- output.add(escapeSnippetString(text.substring(offset, pairOffset)));
-
- // Add this tabstop
- final tabStopText = escapeSnippetString(
- text.substring(pairOffset, pairOffset + pairLength));
- output.add('\${${tabStopNumber++}:$tabStopText}');
-
- offset = pairOffset + pairLength;
- }
-
- // Add any remaining text that was after the last tabstop.
- output.add(escapeSnippetString(text.substring(offset)));
-
- return output.join('');
-}
-
/// Creates a [lsp.WorkspaceEdit] from simple [server.SourceFileEdit]s.
///
/// Note: This code will fetch the version of each document being modified so
@@ -573,15 +530,6 @@
.firstWhere(isSupported, orElse: () => lsp.SymbolKind.Obj);
}
-/// Escapes a string to be used in an LSP edit that uses Snippet mode.
-///
-/// Snippets can contain special markup like `${a:b}` so some characters need
-/// escaping (according to the LSP spec, those are `$`, `}` and `\`).
-String escapeSnippetString(String input) => input.replaceAllMapped(
- RegExp(r'[$}\\]'), // Replace any of $ } \
- (c) => '\\${c[0]}', // Prefix with a backslash
- );
-
String? getCompletionDetail(
server.CompletionSuggestion suggestion,
lsp.CompletionItemKind? completionKind,
@@ -942,6 +890,25 @@
);
}
+/// Creates a SnippetTextEdit for an edit with a selection placeholder.
+///
+/// [selectionOffset] is relative to (and therefore must be within) the edit.
+lsp.SnippetTextEdit snippetTextEditWithSelection(
+ server.LineInfo lineInfo,
+ server.SourceEdit edit, {
+ required int selectionOffsetRelative,
+ int? selectionLength,
+}) {
+ return lsp.SnippetTextEdit(
+ insertTextFormat: lsp.InsertTextFormat.Snippet,
+ range: toRange(lineInfo, edit.offset, edit.length),
+ newText: buildSnippetStringWithTabStops(
+ edit.replacement,
+ [selectionOffsetRelative, selectionLength ?? 0],
+ ),
+ );
+}
+
lsp.CompletionItemKind? suggestionKindToCompletionItemKind(
Set<lsp.CompletionItemKind> supportedCompletionKinds,
server.CompletionSuggestionKind kind,
@@ -1405,20 +1372,6 @@
);
}
-lsp.SnippetTextEdit toSnippetTextEdit(
- LspClientCapabilities capabilities,
- server.LineInfo lineInfo,
- server.SourceEdit edit,
- int selectionOffsetRelative,
- int? selectionLength) {
- return lsp.SnippetTextEdit(
- insertTextFormat: lsp.InsertTextFormat.Snippet,
- range: toRange(lineInfo, edit.offset, edit.length),
- newText: buildSnippetStringWithTabStops(
- edit.replacement, [selectionOffsetRelative, selectionLength ?? 0]),
- );
-}
-
ErrorOr<server.SourceRange> toSourceRange(
server.LineInfo lineInfo, Range range) {
// If there is a range, convert to offsets because that's what
@@ -1467,8 +1420,9 @@
toTextEdit(lineInfo, edit));
}
return Either3<lsp.SnippetTextEdit, lsp.AnnotatedTextEdit, lsp.TextEdit>.t1(
- toSnippetTextEdit(capabilities, lineInfo, edit, selectionOffsetRelative,
- selectionLength));
+ snippetTextEditWithSelection(lineInfo, edit,
+ selectionOffsetRelative: selectionOffsetRelative,
+ selectionLength: selectionLength));
}
lsp.TextEdit toTextEdit(server.LineInfo lineInfo, server.SourceEdit edit) {
@@ -1580,21 +1534,20 @@
insertTextFormat = lsp.InsertTextFormat.Snippet;
final hasRequiredParameters =
(defaultArgumentListTextRanges?.length ?? 0) > 0;
- final functionCallSuffix = hasRequiredParameters
- ? buildSnippetStringWithTabStops(
- defaultArgumentListString,
- defaultArgumentListTextRanges,
- )
- : '\${0:}'; // No required params still gets a tabstop in the parens.
- insertText = '${escapeSnippetString(insertText)}($functionCallSuffix)';
+ final functionCallSuffix =
+ hasRequiredParameters && defaultArgumentListString != null
+ ? buildSnippetStringWithTabStops(
+ defaultArgumentListString, defaultArgumentListTextRanges)
+ // No required params still gets a final tab stop in the parens.
+ : SnippetBuilder.finalTabStop;
+ insertText =
+ '${SnippetBuilder.escapeSnippetPlainText(insertText)}($functionCallSuffix)';
} else if (selectionOffset != 0 &&
- // We don't need a tabstop if the selection is the end of the string.
+ // We don't need a tab stop if the selection is the end of the string.
selectionOffset != completion.length) {
insertTextFormat = lsp.InsertTextFormat.Snippet;
insertText = buildSnippetStringWithTabStops(
- completion,
- [selectionOffset, selectionLength],
- );
+ completion, [selectionOffset, selectionLength]);
}
}
diff --git a/pkg/analysis_server/lib/src/lsp/snippets.dart b/pkg/analysis_server/lib/src/lsp/snippets.dart
new file mode 100644
index 0000000..6f59d8e
--- /dev/null
+++ b/pkg/analysis_server/lib/src/lsp/snippets.dart
@@ -0,0 +1,241 @@
+// 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:math' as math;
+
+import 'package:collection/collection.dart';
+
+/// Builds an LSP snippet string with supplied ranges as tab stops.
+///
+/// [tabStopOffsetLengthPairs] are relative to the supplied text.
+String buildSnippetStringWithTabStops(
+ String text,
+ List<int>? tabStopOffsetLengthPairs,
+) {
+ tabStopOffsetLengthPairs ??= const [];
+ assert(tabStopOffsetLengthPairs.length % 2 == 0);
+
+ // Convert selection/tab stops/edit groups all into a common format
+ // (`SnippetPlaceholder`) so they can be handled in a single pass through
+ // the text.
+ final placeholders = [
+ // Tab stops.
+ for (var i = 0; i < tabStopOffsetLengthPairs.length - 1; i += 2)
+ SnippetPlaceholder(
+ tabStopOffsetLengthPairs[i],
+ tabStopOffsetLengthPairs[i + 1],
+ // If there's only a single tab stop, mark
+ // it as the final stop so it exit "snippet mode" when tabbed to.
+ isFinal: tabStopOffsetLengthPairs.length == 2,
+ ),
+ // TODO(dantup): Add edit group/selection support.
+ ];
+
+ // Remove any groups outside of the range (it's possible the edit groups apply
+ // to a different edit in the collection).
+ placeholders.removeWhere((placeholder) =>
+ placeholder.offset < 0 ||
+ placeholder.offset + placeholder.length > text.length);
+
+ final builder = SnippetBuilder()..appendPlaceholders(text, placeholders);
+ return builder.value;
+}
+
+/// A helper for building for snippets using LSP/TextMate syntax.
+///
+/// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax
+///
+/// - $1, $2, etc. are used for tab stops
+/// - ${1:foo} inserts a placeholder of foo
+/// - ${1|foo,bar|} inserts a placeholder of foo with a selection list
+/// containing "foo" and "bar"
+class SnippetBuilder {
+ /// The constant `$0` used do indicate a final tab stop in the snippet syntax.
+ static const finalTabStop = r'$0';
+
+ final _buffer = StringBuffer();
+
+ var _nextPlaceholder = 1;
+
+ /// The built snippet text using the LSP snippet syntax.
+ String get value => _buffer.toString();
+
+ /// Appends a placeholder with a set of choices to choose from.
+ ///
+ /// If there are 0 or 1 choices, a placeholder will be inserted instead.
+ ///
+ /// Returns the placeholder number used.
+ int appendChoice(Iterable<String> choices, {int? placeholderNumber}) {
+ final uniqueChoices = choices.where((item) => item.isNotEmpty).toSet();
+
+ // If there's only 0/1 items, we can downgrade this to a placeholder.
+ if (uniqueChoices.length <= 1) {
+ return appendPlaceholder(
+ uniqueChoices.firstOrNull ?? '',
+ placeholderNumber: placeholderNumber,
+ );
+ }
+
+ placeholderNumber = _usePlaceholerNumber(placeholderNumber);
+
+ final escapedChoices = uniqueChoices.map(escapeSnippetChoiceText).join(',');
+ _buffer.write('\${$placeholderNumber|$escapedChoices|}');
+
+ return placeholderNumber;
+ }
+
+ /// Appends a placeholder with the given text.
+ ///
+ /// If the text is empty, inserts a tab stop instead.
+ ///
+ /// Returns the placeholder number used.
+ int appendPlaceholder(String text, {int? placeholderNumber}) {
+ // If there's no text, we can downgrade this to a tab stop.
+ if (text.isEmpty) {
+ return appendTabStop(placeholderNumber: placeholderNumber);
+ }
+
+ placeholderNumber = _usePlaceholerNumber(placeholderNumber);
+
+ final escapedText = escapeSnippetVariableText(text);
+ _buffer.write('\${$placeholderNumber:$escapedText}');
+
+ return placeholderNumber;
+ }
+
+ /// Appends a tab stop.
+ ///
+ /// Returns the placeholder number used.
+ int appendTabStop({int? placeholderNumber}) {
+ placeholderNumber = _usePlaceholerNumber(placeholderNumber);
+
+ _buffer.write('\$$placeholderNumber');
+
+ return placeholderNumber;
+ }
+
+ /// Appends normal text (escaping it as required).
+ void appendText(String text) {
+ _buffer.write(escapeSnippetPlainText(text));
+ }
+
+ /// Generates the current and next placeholder numbers.
+ int _usePlaceholerNumber(int? placeholderNumber) {
+ // If a number was not supplied, use thenext available one.
+ placeholderNumber ??= _nextPlaceholder;
+ // If the number we used was the highest seen, set the next one after it.
+ _nextPlaceholder = math.max(_nextPlaceholder, placeholderNumber + 1);
+
+ return placeholderNumber;
+ }
+
+ /// Escapes a string use inside a "choice" in a snippet.
+ ///
+ /// Similar to [escapeSnippetPlainText], but choices are delimited/separated
+ /// by pipes and commas (`${1:|a,b,c|}`).
+ static String escapeSnippetChoiceText(String input) => _escapeCharacters(
+ input,
+ RegExp(r'[$}\\\|,]'), // Replace any of $ } \ | ,
+ );
+
+ /// Escapes a string to be used in an LSP edit that uses Snippet mode where the
+ /// text is outside of a snippet token.
+ ///
+ /// Snippets can contain special markup like `${a:b}` so `$` needs escaping
+ /// as does `\` so it's not interpreted as an escape.
+ static String escapeSnippetPlainText(String input) => _escapeCharacters(
+ input,
+ RegExp(r'[$\\]'), // Replace any of $ \
+ );
+
+ /// Escapes a string to be used inside a snippet token.
+ ///
+ /// Similar to [escapeSnippetPlainText] but additionally escapes `}` so that the
+ /// token is not ended early if the included text contains braces.
+ static String escapeSnippetVariableText(String input) => _escapeCharacters(
+ input,
+ RegExp(r'[$}\\]'), // Replace any of $ } \
+ );
+
+ /// Escapes [pattern] in [input] with backslashes.
+ static String _escapeCharacters(String input, Pattern pattern) =>
+ input.replaceAllMapped(pattern, (c) => '\\${c[0]}');
+}
+
+/// Information about an individual placeholder/tab stop in a piece of code.
+///
+/// Each placeholder represents a single position into the code, so a linked
+/// edit group with 2 positions will be represented as two instances of this
+/// class (with the same [linkedGroupId]).
+class SnippetPlaceholder {
+ final int offset;
+ final int length;
+ final List<String>? suggestions;
+ final int? linkedGroupId;
+ final bool isFinal;
+
+ SnippetPlaceholder(
+ this.offset,
+ this.length, {
+ this.suggestions,
+ this.linkedGroupId,
+ this.isFinal = false,
+ });
+}
+
+/// Helpers for [SnippetBuilder] that do not relate to building the main snippet
+/// syntax (for example, converting from intermediate structures).
+extension SnippetBuilderExtensions on SnippetBuilder {
+ void appendPlaceholders(String text, List<SnippetPlaceholder> placeholders) {
+ // Ensure placeholders are in the order they're visible in the source so
+ // tabbing through them doesn't appear to jump around.
+ placeholders.sortBy<num>((placeholder) => placeholder.offset);
+
+ // We need to use the same placeholder number for all placeholders in the
+ // same linked group, so the first time we see a linked item, store its
+ // placeholder number here, so subsequent placeholders for the same linked
+ // group can reuse it.
+ final placeholderIdForLinkedGroupId = <int, int>{};
+
+ var offset = 0;
+ for (final placeholder in placeholders) {
+ // Add any text that came before this placeholder to the result.
+ appendText(text.substring(offset, placeholder.offset));
+
+ final linkedGroupId = placeholder.linkedGroupId;
+ int? thisPaceholderNumber;
+ // Override the placeholder number if it's the final one (0) or needs to
+ // re-use an existing one for a linked group.
+ if (placeholder.isFinal) {
+ thisPaceholderNumber = 0;
+ } else if (linkedGroupId != null) {
+ thisPaceholderNumber = placeholderIdForLinkedGroupId[linkedGroupId];
+ }
+
+ // Append the placeholder/choices.
+ final placeholderText = text.substring(
+ placeholder.offset,
+ placeholder.offset + placeholder.length,
+ );
+ // appendChoice handles mapping empty/single suggestions to a normal
+ // placeholder.
+ thisPaceholderNumber = appendChoice(
+ [placeholderText, ...?placeholder.suggestions],
+ placeholderNumber: thisPaceholderNumber,
+ );
+
+ // Track where we're up to.
+ offset = placeholder.offset + placeholder.length;
+
+ // Store the placeholder number used for linked groups so it can be reused
+ // by subsequent references to it.
+ if (linkedGroupId != null) {
+ placeholderIdForLinkedGroupId[linkedGroupId] = thisPaceholderNumber;
+ }
+ }
+
+ // Add any remaining text that was after the last placeholder.
+ appendText(text.substring(offset));
+ }
+}
diff --git a/pkg/analysis_server/test/lsp/completion.dart b/pkg/analysis_server/test/lsp/completion.dart
index 6d70a26..ea8d26b 100644
--- a/pkg/analysis_server/test/lsp/completion.dart
+++ b/pkg/analysis_server/test/lsp/completion.dart
@@ -101,10 +101,10 @@
return null;
}
- /// Replaces the LSP snippet placeholder '${0:}' with '^' for easier verifying
+ /// Replaces the LSP snippet placeholder '$0' with '^' for easier verifying
/// of the cursor position in completions.
String withCaret(String contents, InsertTextFormat? format) =>
format == InsertTextFormat.Snippet
- ? contents.replaceFirst(r'${0:}', '^')
+ ? contents.replaceFirst(r'$0', '^')
: contents;
}
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 01f2b09..1f749a6 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -354,7 +354,7 @@
// Ensure the snippet comes through in the expected format with the expected
// placeholders.
expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
- expect(item.insertText, equals('setState(() {\n \${0:}\n \\});'));
+ expect(item.insertText, equals('setState(() {\n \$0\n });'));
final textEdit = toTextEdit(item.textEdit!);
expect(textEdit.newText, equals(item.insertText));
expect(textEdit.range, equals(rangeFromMarkers(content)));
@@ -396,9 +396,9 @@
await openFile(mainFileUri, withoutMarkers(content));
final res = await getCompletion(mainFileUri, positionFromMarker(content));
final item = res.singleWhere((c) => c.label == 'myFunction(…)');
- // With no required params, there should still be parens and a tabstop inside.
+ // With no required params, there should still be parens/tab stop inside.
expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
- expect(item.insertText, equals(r'myFunction(${0:})'));
+ expect(item.insertText, equals(r'myFunction($0)'));
final textEdit = toTextEdit(item.textEdit!);
expect(textEdit.newText, equals(item.insertText));
expect(textEdit.range, equals(rangeFromMarkers(content)));
@@ -1143,9 +1143,9 @@
// Ensure the snippet comes through in the expected format with the expected
// placeholder.
expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
- expect(item.insertText, equals(r'one: ${0:},'));
+ expect(item.insertText, equals(r'one: $0,'));
final textEdit = toTextEdit(item.textEdit!);
- expect(textEdit.newText, equals(r'one: ${0:},'));
+ expect(textEdit.newText, equals(r'one: $0,'));
expect(
textEdit.range,
equals(Range(
diff --git a/pkg/analysis_server/test/lsp/mapping_test.dart b/pkg/analysis_server/test/lsp/mapping_test.dart
index b55938b..196b547 100644
--- a/pkg/analysis_server/test/lsp/mapping_test.dart
+++ b/pkg/analysis_server/test/lsp/mapping_test.dart
@@ -117,38 +117,6 @@
expect(results2, equals(expectedOrder));
}
- Future<void> test_tabStopsInSnippets_contains() async {
- var result = lsp.buildSnippetStringWithTabStops('a, b, c', [3, 1]);
- expect(result, equals(r'a, ${0:b}, c'));
- }
-
- Future<void> test_tabStopsInSnippets_empty() async {
- var result = lsp.buildSnippetStringWithTabStops('a, b', []);
- expect(result, equals(r'a, b'));
- }
-
- Future<void> test_tabStopsInSnippets_endsWith() async {
- var result = lsp.buildSnippetStringWithTabStops('a, b', [3, 1]);
- expect(result, equals(r'a, ${0:b}'));
- }
-
- Future<void> test_tabStopsInSnippets_escape() async {
- var result = lsp.buildSnippetStringWithTabStops(
- r'te$tstri}ng, te$tstri}ng, te$tstri}ng', [13, 11]);
- expect(result, equals(r'te\$tstri\}ng, ${0:te\$tstri\}ng}, te\$tstri\}ng'));
- }
-
- Future<void> test_tabStopsInSnippets_multiple() async {
- var result =
- lsp.buildSnippetStringWithTabStops('a, b, c', [0, 1, 3, 1, 6, 1]);
- expect(result, equals(r'${1:a}, ${2:b}, ${3:c}'));
- }
-
- Future<void> test_tabStopsInSnippets_startsWith() async {
- var result = lsp.buildSnippetStringWithTabStops('a, b', [0, 1]);
- expect(result, equals(r'${0:a}, b'));
- }
-
/// Verifies that [kind] maps to [expectedKind] when the client supports
/// [supportedKinds].
void verifyCompletionItemKind({
diff --git a/pkg/analysis_server/test/lsp/snippets_test.dart b/pkg/analysis_server/test/lsp/snippets_test.dart
new file mode 100644
index 0000000..ba25212
--- /dev/null
+++ b/pkg/analysis_server/test/lsp/snippets_test.dart
@@ -0,0 +1,168 @@
+// 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:analysis_server/src/lsp/snippets.dart' as lsp;
+import 'package:analysis_server/src/lsp/snippets.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(SnippetsTest);
+ defineReflectiveTests(SnippetBuilderTest);
+ });
+}
+
+@reflectiveTest
+class SnippetBuilderTest {
+ Future<void> test_appendChoice() async {
+ final builder = SnippetBuilder()
+ ..appendChoice({r'a'})
+ ..appendChoice([r'a', r'b', r'a'])
+ ..appendChoice([], placeholderNumber: 6)
+ ..appendChoice([r'aaa', r'bbb'], placeholderNumber: 12)
+ ..appendChoice([r'aaa', r'bbb $ bbb | bbb } bbb']);
+
+ expect(
+ builder.value,
+ r'${1:a}'
+ r'${2|a,b|}'
+ r'$6'
+ r'${12|aaa,bbb|}'
+ r'${13|aaa,bbb \$ bbb \| bbb \} bbb|}',
+ );
+ }
+
+ Future<void> test_appendPlaceholder() async {
+ final builder = SnippetBuilder()
+ ..appendPlaceholder(r'placeholder $ 1')
+ ..appendPlaceholder(r'')
+ ..appendPlaceholder(r'placeholder } 3', placeholderNumber: 6);
+
+ expect(
+ builder.value,
+ r'${1:placeholder \$ 1}'
+ r'$2'
+ r'${6:placeholder \} 3}',
+ );
+ }
+
+ Future<void> test_appendTabStop() async {
+ final builder = SnippetBuilder()
+ ..appendTabStop()
+ ..appendTabStop(placeholderNumber: 10)
+ ..appendTabStop();
+
+ expect(
+ builder.value,
+ r'$1'
+ r'$10'
+ r'$11',
+ );
+ }
+
+ Future<void> test_appendText() async {
+ final builder = SnippetBuilder()
+ ..appendText(r'text 1')
+ ..appendText(r'text ${that needs} escaping $0')
+ ..appendText(r'text 2');
+
+ expect(
+ builder.value,
+ r'text 1'
+ r'text \${that needs} escaping \$0'
+ r'text 2',
+ );
+ }
+
+ Future<void> test_extension_appendPlaceholders() async {
+ final code = r'''
+012345678
+012345678
+012345678
+012345678
+012345678
+''';
+
+ final placeholders = [
+ lsp.SnippetPlaceholder(2, 2),
+ lsp.SnippetPlaceholder(12, 2, isFinal: true),
+ lsp.SnippetPlaceholder(22, 2, suggestions: ['aaa', 'bbb']),
+ lsp.SnippetPlaceholder(32, 2, linkedGroupId: 123),
+ lsp.SnippetPlaceholder(42, 2, linkedGroupId: 123),
+ ];
+
+ final builder = SnippetBuilder()..appendPlaceholders(code, placeholders);
+
+ expect(builder.value, r'''
+01${1:23}45678
+01${0:23}45678
+01${2|23,aaa,bbb|}45678
+01${3:23}45678
+01${3:23}45678
+''');
+ }
+
+ Future<void> test_mixed() async {
+ final builder = SnippetBuilder()
+ ..appendText('text1')
+ ..appendPlaceholder('placeholder')
+ ..appendText('text2')
+ ..appendChoice(['aaa', 'bbb'])
+ ..appendText('text3')
+ ..appendTabStop()
+ ..appendText('text4');
+
+ expect(
+ builder.value,
+ r'text1'
+ r'${1:placeholder}'
+ r'text2'
+ r'${2|aaa,bbb|}'
+ r'text3'
+ r'$3'
+ r'text4',
+ );
+ }
+}
+
+@reflectiveTest
+class SnippetsTest {
+ Future<void> test_tabStops_contains() async {
+ var result = lsp.buildSnippetStringWithTabStops('a, b, c', [3, 1]);
+ expect(result, equals(r'a, ${0:b}, c'));
+ }
+
+ Future<void> test_tabStops_empty() async {
+ var result = lsp.buildSnippetStringWithTabStops('a, b', []);
+ expect(result, equals(r'a, b'));
+ }
+
+ Future<void> test_tabStops_endsWith() async {
+ var result = lsp.buildSnippetStringWithTabStops('a, b', [3, 1]);
+ expect(result, equals(r'a, ${0:b}'));
+ }
+
+ Future<void> test_tabStops_escape() async {
+ var result = lsp.buildSnippetStringWithTabStops(
+ r'te$tstri}ng, te$tstri}ng, te$tstri}ng', [13, 11]);
+ expect(result, equals(r'te\$tstri}ng, ${0:te\$tstri\}ng}, te\$tstri}ng'));
+ }
+
+ Future<void> test_tabStops_multiple() async {
+ var result =
+ lsp.buildSnippetStringWithTabStops('a, b, c', [0, 1, 3, 1, 6, 1]);
+ expect(result, equals(r'${1:a}, ${2:b}, ${3:c}'));
+ }
+
+ Future<void> test_tabStops_null() async {
+ var result = lsp.buildSnippetStringWithTabStops('a, b', null);
+ expect(result, equals(r'a, b'));
+ }
+
+ Future<void> test_tabStops_startsWith() async {
+ var result = lsp.buildSnippetStringWithTabStops('a, b', [0, 1]);
+ expect(result, equals(r'${0:a}, b'));
+ }
+}
diff --git a/pkg/analysis_server/test/lsp/test_all.dart b/pkg/analysis_server/test/lsp/test_all.dart
index bd22081..5e5c5f5 100644
--- a/pkg/analysis_server/test/lsp/test_all.dart
+++ b/pkg/analysis_server/test/lsp/test_all.dart
@@ -41,6 +41,7 @@
import 'semantic_tokens_test.dart' as semantic_tokens;
import 'server_test.dart' as server;
import 'signature_help_test.dart' as signature_help;
+import 'snippets_test.dart' as snippets;
import 'super_test.dart' as get_super;
import 'will_rename_files_test.dart' as will_rename_files;
import 'workspace_symbols_test.dart' as workspace_symbols;
@@ -85,6 +86,7 @@
semantic_tokens.main();
server.main();
signature_help.main();
+ snippets.main();
will_rename_files.main();
workspace_symbols.main();
}, name: 'lsp');
diff --git a/pkg/analysis_server/tool/spec/codegen_protocol_constants.dart b/pkg/analysis_server/tool/spec/codegen_protocol_constants.dart
index f6cf4f0..3c16118 100644
--- a/pkg/analysis_server/tool/spec/codegen_protocol_constants.dart
+++ b/pkg/analysis_server/tool/spec/codegen_protocol_constants.dart
@@ -71,7 +71,7 @@
writeln(';');
}
- /// Generate all of the constants associates with the [api].
+ /// Generate all of the constants associated with the [api].
void generateConstants() {
writeln("const String PROTOCOL_VERSION = '${api.version}';");
writeln();
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
index ebb810a..a1bd021 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:collection';
-import 'dart:math' as math;
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
@@ -541,17 +540,16 @@
_addEdit(edit);
}
- /// Add the edit from the given [edit] to the edits associates with the
+ /// Add the edit from the given [edit] to the edits associated with the
/// current file.
void _addEdit(SourceEdit edit) {
fileEdit.add(edit);
var delta = _editDelta(edit);
- changeBuilder._updatePositions(
- edit.offset + math.max<int>(0, delta), delta);
+ changeBuilder._updatePositions(edit.offset, delta);
changeBuilder._lockedPositions.clear();
}
- /// Add the edit from the given [builder] to the edits associates with the
+ /// Add the edit from the given [builder] to the edits associated with the
/// current file.
void _addEditBuilder(EditBuilderImpl builder) {
var edit = builder.sourceEdit;
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
index a571f04..39598e0 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
@@ -384,6 +384,25 @@
});
}
+ Future<void> test_addInsertion_updatesLinkedPositions() async {
+ var groupName = 'a';
+ var range = SourceRange(3, 6);
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addLinkedPosition(range, groupName);
+ // Insert 50 characters before the linked position.
+ builder.addInsertion(0, (builder) => builder.write('// ${'a' * 46}\n'));
+ });
+
+ var group = builder.getLinkedEditGroup(groupName);
+ var positions = group.positions;
+ expect(positions, hasLength(1));
+ var position = positions[0];
+ expect(position.file, path);
+ // Expect the linked position was moved along by the edit.
+ expect(position.offset, range.offset + 50);
+ expect(group.length, range.length);
+ }
+
Future<void> test_addLinkedPosition() async {
var groupName = 'a';
await builder.addGenericFileEdit(path, (builder) {
diff --git a/pkg/analyzer_plugin/tool/spec/codegen_protocol_constants.dart b/pkg/analyzer_plugin/tool/spec/codegen_protocol_constants.dart
index 648859a..611e583 100644
--- a/pkg/analyzer_plugin/tool/spec/codegen_protocol_constants.dart
+++ b/pkg/analyzer_plugin/tool/spec/codegen_protocol_constants.dart
@@ -25,7 +25,7 @@
codeGeneratorSettings.languageName = 'dart';
}
- /// Generate all of the constants associates with the [api].
+ /// Generate all of the constants associated with the [api].
void generateConstants() {
var visitor = _ConstantVisitor(api);
visitor.visitApi();
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect
index f5fb218..1531bb7 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect
@@ -55,7 +55,7 @@
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] no-such-method-forwarder method foo() → dynamic
return _in::unsafeCast<dynamic>([@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=#lib::T1 (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C5, 0, #C2, #C3, [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C4))){(core::Invocation) → dynamic});
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:4,getterSelectorId:5] [@vm.unboxing-info.metadata=(i,i,i,i)->b] no-such-method-forwarder method bazz([@vm.inferred-type.metadata=dart.core::_Smi (value: 1)] dynamic a1, [@vm.inferred-type.metadata=dart.core::_Smi (value: 2)] dynamic a2, [@vm.inferred-type.metadata=dart.core::_Smi (value: 3)] dynamic a3, [@vm.inferred-type.metadata=dart.core::_Smi (value: 4)] dynamic a4) → dynamic
- return _in::unsafeCast<dynamic>([@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=#lib::T1 (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C6, 0, #C2, core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal5<dynamic>()), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C4))){(core::Invocation) → dynamic});
+ return _in::unsafeCast<dynamic>([@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=#lib::T1 (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C6, 0, #C2, [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal5<dynamic>()), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C4))){(core::Invocation) → dynamic});
}
abstract class C extends core::Object {
synthetic constructor •() → self::C
@@ -74,7 +74,7 @@
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] no-such-method-forwarder method foo() → dynamic
return _in::unsafeCast<dynamic>([@vm.direct-call.metadata=#lib::C.noSuchMethod] [@vm.inferred-type.metadata=#lib::T2 (skip check)] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withType(#C5, 0, #C2, #C3, [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C4))){(core::Invocation) → dynamic});
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:4,getterSelectorId:5] [@vm.unboxing-info.metadata=(i,i,i,i)->b] no-such-method-forwarder method bazz([@vm.inferred-type.metadata=dart.core::_Smi (value: 1)] dynamic a1, [@vm.inferred-type.metadata=dart.core::_Smi (value: 2)] dynamic a2, [@vm.inferred-type.metadata=dart.core::_Smi (value: 3)] dynamic a3, [@vm.inferred-type.metadata=dart.core::_Smi (value: 4)] dynamic a4) → dynamic
- return _in::unsafeCast<dynamic>([@vm.direct-call.metadata=#lib::C.noSuchMethod] [@vm.inferred-type.metadata=#lib::T2 (skip check)] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withType(#C6, 0, #C2, core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal5<dynamic>()), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C4))){(core::Invocation) → dynamic});
+ return _in::unsafeCast<dynamic>([@vm.direct-call.metadata=#lib::C.noSuchMethod] [@vm.inferred-type.metadata=#lib::T2 (skip check)] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withType(#C6, 0, #C2, [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal5<dynamic>()), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C4))){(core::Invocation) → dynamic});
}
class E extends core::Object implements self::A {
synthetic constructor •() → self::E
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter81068.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter81068.dart.expect
index 4c3106f..963f8c2 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter81068.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter81068.dart.expect
@@ -17,11 +17,11 @@
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] method noSuchMethod(core::Invocation i) → dynamic
return throw "Not implemented";
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4] no-such-method-forwarder method /* from org-dartlang-sdk:///sdk/lib/async/future.dart */ catchError(core::Function onError) → asy::Future<self::B::T%>
- return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C1, 0, #C2, core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(onError)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(<core::Symbol*, dynamic>{#C3: #C4}))){(core::Invocation) → dynamic} as{TypeError,ForDynamic,ForNonNullableByDefault} asy::Future<self::B::T%>;
+ return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C1, 0, #C2, [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(onError)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(<core::Symbol*, dynamic>{#C3: #C4}))){(core::Invocation) → dynamic} as{TypeError,ForDynamic,ForNonNullableByDefault} asy::Future<self::B::T%>;
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6] no-such-method-forwarder method /* from org-dartlang-sdk:///sdk/lib/async/future.dart */ whenComplete(() → FutureOr<void>action) → asy::Future<self::B::T%>
- return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C5, 0, #C2, core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(action)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C6))){(core::Invocation) → dynamic} as{TypeError,ForDynamic,ForNonNullableByDefault} asy::Future<self::B::T%>;
+ return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C5, 0, #C2, [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(action)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C6))){(core::Invocation) → dynamic} as{TypeError,ForDynamic,ForNonNullableByDefault} asy::Future<self::B::T%>;
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:7,getterSelectorId:8] no-such-method-forwarder method /* from org-dartlang-sdk:///sdk/lib/async/future.dart */ then<R extends core::Object? = dynamic>((self::B::T%) → FutureOr<self::B::then::R%>onValue, {core::Function? onError = #C4}) → asy::Future<self::B::then::R%>
- return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C7, 0, core::List::unmodifiable<core::Type*>([@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::Type*>] core::_GrowableList::_literal1<core::Type*>(self::B::then::R%)), core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(onValue)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(<core::Symbol*, dynamic>{#C8: onError}))){(core::Invocation) → dynamic} as{TypeError,ForDynamic,ForNonNullableByDefault} asy::Future<self::B::then::R%>;
+ return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C7, 0, [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<core::Type*>([@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::Type*>] core::_GrowableList::_literal1<core::Type*>(self::B::then::R%)), [@vm.inferred-type.metadata=dart.core::_ImmutableList] core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(onValue)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(<core::Symbol*, dynamic>{#C8: onError}))){(core::Invocation) → dynamic} as{TypeError,ForDynamic,ForNonNullableByDefault} asy::Future<self::B::then::R%>;
}
static method createB<T extends core::Object? = dynamic>() → self::B<dynamic>
return new self::B::•<self::createB::T%>();
diff --git a/pkg/vm_service/test/super_constructor_invocation_test.dart b/pkg/vm_service/test/super_constructor_invocation_test.dart
new file mode 100644
index 0000000..7a0d300
--- /dev/null
+++ b/pkg/vm_service/test/super_constructor_invocation_test.dart
@@ -0,0 +1,183 @@
+// 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.
+//
+// SharedOptions=--enable-experiment=super-parameters
+
+// ignore_for_file: experiment_not_enabled
+// @dart=2.17
+
+import 'dart:developer';
+
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+class S<T> {
+ num? n;
+ T? t;
+ String constrName;
+ S({this.n, this.t}) : constrName = "S";
+ S.named({this.t, this.n}) : constrName = "S.named";
+}
+
+class C<T> extends S<T> {
+ C.constr1(String s, {super.t});
+ C.constr2(int i, String s, {super.n}) : super();
+ C.constr3(int i, String s, {super.n, super.t}) : super.named() {
+ debugger();
+ }
+}
+
+class R<T> {
+ final f1;
+ var v1;
+ num i1;
+ T t1;
+ R(this.f1, this.v1, this.i1, this.t1);
+}
+
+class B<T> extends R<T> {
+ // ignore: no_default_super_constructor
+ B(super.f1, super.v1, super.i1, super.t1) {
+ debugger();
+ }
+}
+
+void testMain() {
+ debugger();
+ C.constr3(1, 'abc', n: 3.14, t: 42);
+ B('a', 3.14, 2.718, 42);
+}
+
+late final String isolateId;
+late final String rootLibId;
+
+createInstance(VmService service, String expr) async {
+ return await service.evaluate(
+ isolateId,
+ rootLibId,
+ expr,
+ disableBreakpoints: true,
+ );
+}
+
+evaluateGetter(VmService service, String instanceId, String getter) async {
+ dynamic result = await service.evaluate(isolateId, instanceId, getter);
+ return await service.getObject(isolateId, result.id);
+}
+
+final tests = <IsolateTest>[
+ (VmService service, IsolateRef isolateRef) async {
+ // Initialization
+ isolateId = isolateRef.id!;
+ final isolate = await service.getIsolate(isolateId);
+ rootLibId = isolate.rootLib!.id!;
+ },
+ (VmService service, _) async {
+ dynamic instance = await createInstance(service, 'C.constr1("abc", t: 42)');
+ dynamic result = await evaluateGetter(service, instance.id, 'n');
+ expect(result.valueAsString, 'null');
+ result = await evaluateGetter(service, instance.id, 't');
+ expect(result.valueAsString, '42');
+ result = await evaluateGetter(service, instance.id, 'constrName');
+ expect(result.valueAsString, 'S');
+ result = await service.evaluate(isolateId, instance.id, 'T');
+ expect(result.json['name'], 'int');
+
+ instance = await createInstance(service, 'C.constr1("abc", t: "42")');
+ result = await evaluateGetter(service, instance.id, 'n');
+ expect(result.valueAsString, 'null');
+ result = await evaluateGetter(service, instance.id, 't');
+ expect(result.valueAsString, '42');
+ result = await evaluateGetter(service, instance.id, 'constrName');
+ expect(result.valueAsString, 'S');
+ result = await service.evaluate(isolateId, instance.id, 'T');
+ expect(result.json['name'], 'String');
+ },
+ (VmService service, _) async {
+ dynamic instance = await createInstance(service, 'C.constr2(1, "abc", n: 3.14)');
+ dynamic result = await evaluateGetter(service, instance.id, 'n');
+ expect(result.valueAsString, '3.14');
+ result = await evaluateGetter(service, instance.id, 't');
+ expect(result.valueAsString, 'null');
+ result = await evaluateGetter(service, instance.id, 'constrName');
+ expect(result.valueAsString, 'S');
+ result = await service.evaluate(isolateId, instance.id, 'T');
+ expect(result.json['name'], 'dynamic');
+
+ instance = await createInstance(service, 'C.constr2(1, "abc", n: 2)');
+ result = await evaluateGetter(service, instance.id, 'n');
+ expect(result.valueAsString, '2');
+ result = await evaluateGetter(service, instance.id, 't');
+ expect(result.valueAsString, 'null');
+ result = await evaluateGetter(service, instance.id, 'constrName');
+ expect(result.valueAsString, 'S');
+ result = await service.evaluate(isolateId, instance.id, 'T');
+ expect(result.json['name'], 'dynamic');
+ },
+ (VmService service, _) async {
+ dynamic instance = await createInstance(service, 'C.constr3(1, "abc", n: 42, t: 3.14)');
+ dynamic result = await evaluateGetter(service, instance.id, 'n');
+ expect(result.valueAsString, '42');
+ result = await evaluateGetter(service, instance.id, 't');
+ expect(result.valueAsString, '3.14');
+ result = await evaluateGetter(service, instance.id, 'constrName');
+ expect(result.valueAsString, 'S.named');
+ result = await service.evaluate(isolateId, instance.id, 'T');
+ expect(result.json['name'], 'double');
+
+ instance = await createInstance(service, 'C.constr3(1, "abc", n: 3.14, t: 42)');
+ result = await evaluateGetter(service, instance.id, 'n');
+ expect(result.valueAsString, '3.14');
+ result = await evaluateGetter(service, instance.id, 't');
+ expect(result.valueAsString, '42');
+ result = await evaluateGetter(service, instance.id, 'constrName');
+ expect(result.valueAsString, 'S.named');
+ result = await service.evaluate(isolateId, instance.id, 'T');
+ expect(result.json['name'], 'int');
+ },
+ (VmService service, _) async {
+ dynamic instance = await createInstance(service, 'B(1, 2, 3, 4)');
+ dynamic result = await evaluateGetter(service, instance.id, 'f1');
+ expect(result.valueAsString, '1');
+ result = await evaluateGetter(service, instance.id, 'v1');
+ expect(result.valueAsString, '2');
+ result = await evaluateGetter(service, instance.id, 'i1');
+ expect(result.valueAsString, '3');
+ result = await evaluateGetter(service, instance.id, 't1');
+ expect(result.valueAsString, '4');
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ (VmService service, _) async {
+ dynamic result = await service.evaluateInFrame(isolateId, 0, 'n');
+ expect(result.valueAsString, '3.14');
+ result = await service.evaluateInFrame(isolateId, 0, 't');
+ expect(result.valueAsString, '42');
+ result = await service.evaluateInFrame(isolateId, 0, 'constrName');
+ expect(result.valueAsString, 'S.named');
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ (VmService service, _) async {
+ dynamic result = await service.evaluateInFrame(isolateId, 0, 'f1');
+ expect(result.valueAsString, 'a');
+ result = await service.evaluateInFrame(isolateId, 0, 'v1');
+ expect(result.valueAsString, '3.14');
+ result = await service.evaluateInFrame(isolateId, 0, 'i1');
+ expect(result.valueAsString, '2.718');
+ result = await service.evaluateInFrame(isolateId, 0, 't1');
+ expect(result.valueAsString, '42');
+ }
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'super_constructor_invocation_test.dart',
+ testeeConcurrent: testMain,
+ experiments: ['super-parameters'],
+ );
diff --git a/runtime/vm/compiler/backend/il_riscv.cc b/runtime/vm/compiler/backend/il_riscv.cc
index c9067af..51aa2b4 100644
--- a/runtime/vm/compiler/backend/il_riscv.cc
+++ b/runtime/vm/compiler/backend/il_riscv.cc
@@ -132,8 +132,7 @@
}
case kUnboxedDouble: {
const auto out = locs()->out(0).fpu_reg();
- const intptr_t kDoubleSizeLog2 = 3;
- __ slli(TMP, index, kDoubleSizeLog2 - kSmiTagSize);
+ __ slli(TMP, index, kWordSizeLog2 - kSmiTagSize);
__ add(TMP, TMP, base_reg());
__ LoadDFromOffset(out, TMP, offset());
break;
diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h
index aa35336..c928e03 100644
--- a/runtime/vm/compiler/recognized_methods_list.h
+++ b/runtime/vm/compiler/recognized_methods_list.h
@@ -398,8 +398,8 @@
V(::, _isDartStreamEnabled, Timeline_isDartStreamEnabled, 0xc97aafb3) \
#define INTERNAL_LIB_INTRINSIC_LIST(V) \
- V(::, allocateOneByteString, AllocateOneByteString, 0x9e774214) \
- V(::, allocateTwoByteString, AllocateTwoByteString, 0xa63c7db1) \
+ V(::, allocateOneByteString, AllocateOneByteString, 0x9e7745d5) \
+ V(::, allocateTwoByteString, AllocateTwoByteString, 0xa63c8172) \
V(::, writeIntoOneByteString, WriteIntoOneByteString, 0xd8729161) \
V(::, writeIntoTwoByteString, WriteIntoTwoByteString, 0xcfc7982a) \
diff --git a/sdk/lib/_http/http.dart b/sdk/lib/_http/http.dart
index 06d63d9..5503db6 100644
--- a/sdk/lib/_http/http.dart
+++ b/sdk/lib/_http/http.dart
@@ -1516,6 +1516,46 @@
/// Add credentials to be used for authorizing HTTP requests.
void addCredentials(Uri url, String realm, HttpClientCredentials credentials);
+ /// Sets the function used to create socket connections.
+ ///
+ /// The URL requested (e.g. through [getUrl]) and proxy configuration
+ /// ([f.proxyHost] and [f.proxyPort]) are passed as arguments. [f.proxyHost]
+ /// and [f.proxyPort] will be `null` if the connection is not made through
+ /// a proxy.
+ ///
+ /// Since connections may be reused based on host and port, it is important
+ /// that the function not ignore [f.proxyHost] and [f.proxyPort] if they are
+ /// not `null`. If proxies are not meaningful for the returned [Socket], you
+ /// can set [findProxy] to use a direct connection.
+ ///
+ /// For example:
+ ///
+ /// ```dart
+ /// import "dart:io";
+ ///
+ /// void main() async {
+ /// HttpClient client = HttpClient()
+ /// ..connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) {
+ /// assert(proxyHost == null);
+ /// assert(proxyPort == null);
+ /// var address = InternetAddress("/var/run/docker.sock",
+ /// type: InternetAddressType.unix);
+ /// return Socket.startConnect(address, 0);
+ /// }
+ /// ..findProxy = (Uri uri) => 'DIRECT';
+ ///
+ /// final request = await client.getUrl(Uri.parse("http://ignored/v1.41/info"));
+ /// final response = await request.close();
+ /// print(response.statusCode);
+ /// await response.drain();
+ /// client.close();
+ /// }
+ /// ```
+ void set connectionFactory(
+ Future<ConnectionTask<Socket>> Function(
+ Uri url, String? proxyHost, int? proxyPort)?
+ f);
+
/// Sets the function used to resolve the proxy server to be used for
/// opening a HTTP connection to the specified [url]. If this
/// function is not set, direct connections will always be used.
diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart
index b97025a..bb38ebe 100644
--- a/sdk/lib/_http/http_impl.dart
+++ b/sdk/lib/_http/http_impl.dart
@@ -2338,14 +2338,16 @@
final int port;
final bool isSecure;
final SecurityContext? context;
+ final Future<ConnectionTask<Socket>> Function(Uri, String?, int?)?
+ connectionFactory;
final Set<_HttpClientConnection> _idle = HashSet();
final Set<_HttpClientConnection> _active = HashSet();
final Set<ConnectionTask<Socket>> _socketTasks = HashSet();
final _pending = ListQueue<void Function()>();
int _connecting = 0;
- _ConnectionTarget(
- this.key, this.host, this.port, this.isSecure, this.context);
+ _ConnectionTarget(this.key, this.host, this.port, this.isSecure, this.context,
+ this.connectionFactory);
bool get isEmpty => _idle.isEmpty && _active.isEmpty && _connecting == 0;
@@ -2410,8 +2412,8 @@
}
}
- Future<_ConnectionInfo> connect(String uriHost, int uriPort, _Proxy proxy,
- _HttpClient client, _HttpProfileData? profileData) {
+ Future<_ConnectionInfo> connect(Uri uri, String uriHost, int uriPort,
+ _Proxy proxy, _HttpClient client, _HttpProfileData? profileData) {
if (hasIdle) {
var connection = takeIdle();
client._connectionsChanged();
@@ -2422,8 +2424,8 @@
_active.length + _connecting >= maxConnectionsPerHost) {
var completer = Completer<_ConnectionInfo>();
_pending.add(() {
- completer
- .complete(connect(uriHost, uriPort, proxy, client, profileData));
+ completer.complete(
+ connect(uri, uriHost, uriPort, proxy, client, profileData));
});
return completer.future;
}
@@ -2434,10 +2436,20 @@
return currentBadCertificateCallback(certificate, uriHost, uriPort);
}
- Future<ConnectionTask<Socket>> connectionTask = (isSecure && proxy.isDirect
- ? SecureSocket.startConnect(host, port,
- context: context, onBadCertificate: callback)
- : Socket.startConnect(host, port));
+ Future<ConnectionTask<Socket>> connectionTask;
+ final cf = connectionFactory;
+ if (cf != null) {
+ if (proxy.isDirect) {
+ connectionTask = cf(uri, null, null);
+ } else {
+ connectionTask = cf(uri, host, port);
+ }
+ } else {
+ connectionTask = (isSecure && proxy.isDirect
+ ? SecureSocket.startConnect(host, port,
+ context: context, onBadCertificate: callback)
+ : Socket.startConnect(host, port));
+ }
_connecting++;
return connectionTask.then((ConnectionTask<Socket> task) {
_socketTasks.add(task);
@@ -2506,6 +2518,8 @@
final List<_Credentials> _credentials = [];
final List<_ProxyCredentials> _proxyCredentials = [];
final SecurityContext? _context;
+ Future<ConnectionTask<Socket>> Function(Uri, String?, int?)?
+ _connectionFactory;
Future<bool> Function(Uri, String scheme, String? realm)? _authenticate;
Future<bool> Function(String host, int port, String scheme, String? realm)?
_authenticateProxy;
@@ -2631,6 +2645,12 @@
_ProxyCredentials(host, port, realm, cr as _HttpClientCredentials));
}
+ void set connectionFactory(
+ Future<ConnectionTask<Socket>> Function(
+ Uri url, String? proxyHost, int? proxyPort)?
+ f) =>
+ _connectionFactory = f;
+
set findProxy(String Function(Uri uri)? f) => _findProxy = f;
static void _startRequestTimelineEvent(
@@ -2665,7 +2685,9 @@
if (method != "CONNECT") {
if (uri.host.isEmpty) {
throw ArgumentError("No host specified in URI $uri");
- } else if (!uri.isScheme("http") && !uri.isScheme("https")) {
+ } else if (this._connectionFactory == null &&
+ !uri.isScheme("http") &&
+ !uri.isScheme("https")) {
throw ArgumentError("Unsupported scheme '${uri.scheme}' in URI $uri");
}
}
@@ -2695,7 +2717,7 @@
if (HttpClient.enableTimelineLogging) {
profileData = HttpProfiler.startRequest(method, uri);
}
- return _getConnection(uri.host, port, proxyConf, isSecure, profileData)
+ return _getConnection(uri, uri.host, port, proxyConf, isSecure, profileData)
.then((_ConnectionInfo info) {
_HttpClientRequest send(_ConnectionInfo info) {
profileData?.requestEvent('Connection established');
@@ -2706,7 +2728,8 @@
// If the connection was closed before the request was sent, create
// and use another connection.
if (info.connection.closed) {
- return _getConnection(uri.host, port, proxyConf, isSecure, profileData)
+ return _getConnection(
+ uri, uri.host, port, proxyConf, isSecure, profileData)
.then(send);
}
return send(info);
@@ -2813,12 +2836,14 @@
_ConnectionTarget _getConnectionTarget(String host, int port, bool isSecure) {
String key = _HttpClientConnection.makeKey(isSecure, host, port);
return _connectionTargets.putIfAbsent(key, () {
- return _ConnectionTarget(key, host, port, isSecure, _context);
+ return _ConnectionTarget(
+ key, host, port, isSecure, _context, _connectionFactory);
});
}
// Get a new _HttpClientConnection, from the matching _ConnectionTarget.
Future<_ConnectionInfo> _getConnection(
+ Uri uri,
String uriHost,
int uriPort,
_ProxyConfiguration proxyConf,
@@ -2832,7 +2857,7 @@
String host = proxy.isDirect ? uriHost : proxy.host!;
int port = proxy.isDirect ? uriPort : proxy.port!;
return _getConnectionTarget(host, port, isSecure)
- .connect(uriHost, uriPort, proxy, this, profileData)
+ .connect(uri, uriHost, uriPort, proxy, this, profileData)
// On error, continue with next proxy.
.catchError(connect);
}
diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart
index e0267ab..4c0fa92 100644
--- a/sdk/lib/_internal/vm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/vm/lib/internal_patch.dart
@@ -26,10 +26,12 @@
@patch
@pragma("vm:external-name", "Internal_makeListFixedLength")
+@pragma("vm:exact-result-type", "dart:core#_List")
external List<T> makeListFixedLength<T>(List<T> growableList);
@patch
@pragma("vm:external-name", "Internal_makeFixedListUnmodifiable")
+@pragma("vm:exact-result-type", "dart:core#_ImmutableList")
external List<T> makeFixedListUnmodifiable<T>(List<T> fixedLengthList);
@patch
@@ -39,6 +41,7 @@
/// The returned string is a [_OneByteString] with uninitialized content.
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:external-name", "Internal_allocateOneByteString")
+@pragma("vm:exact-result-type", "dart:core#_OneByteString")
external String allocateOneByteString(int length);
/// The [string] must be a [_OneByteString]. The [index] must be valid.
@@ -61,6 +64,7 @@
/// The returned string is a [_TwoByteString] with uninitialized content.
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:external-name", "Internal_allocateTwoByteString")
+@pragma("vm:exact-result-type", "dart:core#_TwoByteString")
external String allocateTwoByteString(int length);
/// The [string] must be a [_TwoByteString]. The [index] must be valid.
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 0c9b5d0..98fe3f6 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -33717,7 +33717,7 @@
* See [EventStreamProvider] for usage information.
*/
static const EventStreamProvider<BeforeUnloadEvent> beforeUnloadEvent =
- const _BeforeUnloadEventStreamProvider('beforeunload');
+ const EventStreamProvider('beforeunload');
/// Stream of `beforeunload` events handled by this [Window].
Stream<Event> get onBeforeUnload => beforeUnloadEvent.forTarget(this);
@@ -33773,65 +33773,6 @@
? JS<num>('num', '#.scrollY', this).round()
: document.documentElement!.scrollTop;
}
-
-class _BeforeUnloadEvent extends _WrappedEvent implements BeforeUnloadEvent {
- String _returnValue;
-
- _BeforeUnloadEvent(Event base)
- : _returnValue = '',
- super(base);
-
- String get returnValue => _returnValue;
-
- set returnValue(String? value) {
- // Typed as nullable only to be compatible with the overriden method.
- _returnValue = value!;
- // FF and IE use the value as the return value, Chrome will return this from
- // the event callback function.
- if (JS<bool>('bool', '("returnValue" in #)', wrapped)) {
- JS('void', '#.returnValue = #', wrapped, value);
- }
- }
-}
-
-class _BeforeUnloadEventStreamProvider
- implements EventStreamProvider<BeforeUnloadEvent> {
- final String _eventType;
-
- const _BeforeUnloadEventStreamProvider(this._eventType);
-
- Stream<BeforeUnloadEvent> forTarget(EventTarget? e,
- {bool useCapture: false}) {
- // Specify the generic type for EventStream only in dart2js.
- var stream = new _EventStream<BeforeUnloadEvent>(e, _eventType, useCapture);
- var controller = new StreamController<BeforeUnloadEvent>(sync: true);
-
- stream.listen((event) {
- var wrapped = new _BeforeUnloadEvent(event);
- controller.add(wrapped);
- });
-
- return controller.stream;
- }
-
- String getEventType(EventTarget target) {
- return _eventType;
- }
-
- ElementStream<BeforeUnloadEvent> forElement(Element e,
- {bool useCapture: false}) {
- // Specify the generic type for _ElementEventStreamImpl only in dart2js.
- return new _ElementEventStreamImpl<BeforeUnloadEvent>(
- e, _eventType, useCapture);
- }
-
- ElementStream<BeforeUnloadEvent> _forElementList(ElementList<Element> e,
- {bool useCapture: false}) {
- // Specify the generic type for _ElementEventStreamImpl only in dart2js.
- return new _ElementListEventStreamImpl<BeforeUnloadEvent>(
- e, _eventType, useCapture);
- }
-}
// 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.
diff --git a/tests/standalone/io/http_connection_factory_test.dart b/tests/standalone/io/http_connection_factory_test.dart
new file mode 100644
index 0000000..6cc41cd
--- /dev/null
+++ b/tests/standalone/io/http_connection_factory_test.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:io";
+import 'dart:convert';
+import "package:expect/expect.dart";
+import 'http_proxy_test.dart' show setupProxyServer;
+import 'test_utils.dart' show withTempDir;
+
+testDirectConnection() async {
+ var server = await HttpServer.bind(InternetAddress.anyIPv6, 0);
+ server.forEach((HttpRequest request) {
+ request.response.write('Hello, world!');
+ request.response.close();
+ });
+ final serverUri = Uri.http("127.0.0.1:${server.port}", "/");
+ var client = HttpClient()
+ ..connectionFactory = (uri, proxyHost, proxyPort) {
+ Expect.isNull(proxyHost);
+ Expect.isNull(proxyPort);
+ Expect.equals(serverUri, uri);
+ return Socket.startConnect(uri.host, uri.port);
+ }
+ ..findProxy = (uri) => 'DIRECT';
+ final response = await client.getUrl(serverUri).then((request) {
+ return request.close();
+ });
+ Expect.equals(200, response.statusCode);
+ final responseText = await response
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello, world!", responseText);
+ client.close();
+ server.close();
+}
+
+testConnectionViaProxy() async {
+ var proxyServer = await setupProxyServer();
+ var server = await HttpServer.bind(InternetAddress.anyIPv6, 0);
+ server.forEach((HttpRequest request) {
+ request.response.write('Hello via Proxy');
+ request.response.close();
+ });
+ final serverUri = Uri.http("127.0.0.1:${server.port}", "/");
+ final client = HttpClient()
+ ..connectionFactory = (uri, proxyHost, proxyPort) {
+ Expect.equals("localhost", proxyHost);
+ Expect.equals(proxyServer.port, proxyPort);
+ Expect.equals(serverUri, uri);
+ return Socket.startConnect(proxyHost, proxyPort as int);
+ }
+ ..findProxy = (uri) => "PROXY localhost:${proxyServer.port}";
+ final response = await client.getUrl(serverUri).then((request) {
+ return request.close();
+ });
+ Expect.equals(200, response.statusCode);
+ final responseText = await response
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello via Proxy", responseText);
+ client.close();
+ server.close();
+ proxyServer.shutdown();
+}
+
+testDifferentAddressFamiliesAndProxySettings(String dir) async {
+ // Test a custom connection factory for Unix domain sockets that also allows
+ // regular INET/INET6 access with and without a proxy.
+ var proxyServer = await setupProxyServer();
+ var inet6Server = await HttpServer.bind(InternetAddress.anyIPv6, 0);
+ inet6Server.forEach((HttpRequest request) {
+ request.response.write('Hello via Proxy');
+ request.response.close();
+ });
+ final inet6ServerUri = Uri.http("127.0.0.1:${inet6Server.port}", "/");
+ final unixPath = '$dir/sock';
+ final unixAddress = InternetAddress(unixPath, type: InternetAddressType.unix);
+ final unixServer = await HttpServer.bind(unixAddress, 0);
+ unixServer.forEach((HttpRequest request) {
+ request.response.write('Hello via Unix');
+ request.response.close();
+ });
+ final client = HttpClient()
+ ..connectionFactory = (uri, proxyHost, proxyPort) {
+ if (uri.scheme == 'unix') {
+ assert(proxyHost == null);
+ assert(proxyPort == null);
+ var address = InternetAddress(unixPath, type: InternetAddressType.unix);
+ return Socket.startConnect(address, 0);
+ } else {
+ if (proxyHost != null && proxyPort != null) {
+ return Socket.startConnect(proxyHost, proxyPort);
+ }
+ return Socket.startConnect(uri.host, uri.port);
+ }
+ }
+ ..findProxy = (uri) {
+ if (uri.scheme == 'unix') {
+ // Proxy settings are not meaningful for Unix domain sockets.
+ return 'DIRECT';
+ } else {
+ return "PROXY localhost:${proxyServer.port}";
+ }
+ };
+ // Fetch a URL from the INET6 server and verify the results.
+ final inet6Response = await client.getUrl(inet6ServerUri).then((request) {
+ return request.close();
+ });
+ Expect.equals(200, inet6Response.statusCode);
+ final inet6ResponseText = await inet6Response
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello via Proxy", inet6ResponseText);
+ // Fetch a URL from the Unix server and verify the results.
+ final unixResponse = await client
+ .getUrl(Uri(
+ scheme: "unix",
+ // Connection pooling is based on the host/port combination
+ // so ensure that the host is unique for unique logical
+ // endpoints. Also, the `host` property is converted to
+ // lowercase so you cannot use it directly for file paths.
+ host: 'dummy',
+ path: "/"))
+ .then((request) {
+ return request.close();
+ });
+ Expect.equals(200, unixResponse.statusCode);
+ final unixResponseText = await unixResponse
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello via Unix", unixResponseText);
+ client.close();
+ inet6Server.close();
+ unixServer.close();
+ proxyServer.shutdown();
+}
+
+main() async {
+ await testDirectConnection();
+ await testConnectionViaProxy();
+ if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) {
+ await withTempDir('unix_socket_test', (Directory dir) async {
+ await testDifferentAddressFamiliesAndProxySettings('${dir.path}');
+ });
+ }
+}
diff --git a/tests/standalone/io/http_override_test.dart b/tests/standalone/io/http_override_test.dart
index 4be81c7..92270d0 100644
--- a/tests/standalone/io/http_override_test.dart
+++ b/tests/standalone/io/http_override_test.dart
@@ -41,6 +41,10 @@
set authenticate(Future<bool> f(Uri url, String scheme, String realm)?) {}
void addCredentials(
Uri url, String realm, HttpClientCredentials credentials) {}
+ set connectionFactory(
+ Future<ConnectionTask<Socket>> Function(
+ Uri url, String? proxyHost, int? proxyPort)?
+ f) {}
set findProxy(String f(Uri url)?) {}
set authenticateProxy(
Future<bool> f(String host, int port, String scheme, String realm)?) {}
@@ -85,6 +89,10 @@
set authenticate(Future<bool> f(Uri url, String scheme, String realm)?) {}
void addCredentials(
Uri url, String realm, HttpClientCredentials credentials) {}
+ set connectionFactory(
+ Future<ConnectionTask<Socket>> Function(
+ Uri url, String? proxyHost, int? proxyPort)?
+ f) {}
set findProxy(String f(Uri url)?) {}
set authenticateProxy(
Future<bool> f(String host, int port, String scheme, String realm)?) {}
diff --git a/tests/standalone_2/io/http_connection_factory_test.dart b/tests/standalone_2/io/http_connection_factory_test.dart
new file mode 100644
index 0000000..eda4ac2
--- /dev/null
+++ b/tests/standalone_2/io/http_connection_factory_test.dart
@@ -0,0 +1,147 @@
+// 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.
+// @dart = 2.9
+import "dart:io";
+import 'dart:convert';
+import "package:expect/expect.dart";
+import 'http_proxy_test.dart' show setupProxyServer;
+import 'test_utils.dart' show withTempDir;
+
+testDirectConnection() async {
+ var server = await HttpServer.bind(InternetAddress.anyIPv6, 0);
+ server.forEach((HttpRequest request) {
+ request.response.write('Hello, world!');
+ request.response.close();
+ });
+ final serverUri = Uri.http("127.0.0.1:${server.port}", "/");
+ var client = HttpClient()
+ ..connectionFactory = (uri, proxyHost, proxyPort) {
+ Expect.isNull(proxyHost);
+ Expect.isNull(proxyPort);
+ Expect.equals(serverUri, uri);
+ return Socket.startConnect(uri.host, uri.port);
+ }
+ ..findProxy = (uri) => 'DIRECT';
+ final response = await client.getUrl(serverUri).then((request) {
+ return request.close();
+ });
+ Expect.equals(200, response.statusCode);
+ final responseText = await response
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello, world!", responseText);
+ client.close();
+ server.close();
+}
+
+testConnectionViaProxy() async {
+ var proxyServer = await setupProxyServer();
+ var server = await HttpServer.bind(InternetAddress.anyIPv6, 0);
+ server.forEach((HttpRequest request) {
+ request.response.write('Hello via Proxy');
+ request.response.close();
+ });
+ final serverUri = Uri.http("127.0.0.1:${server.port}", "/");
+ final client = HttpClient()
+ ..connectionFactory = (uri, proxyHost, proxyPort) {
+ Expect.equals("localhost", proxyHost);
+ Expect.equals(proxyServer.port, proxyPort);
+ Expect.equals(serverUri, uri);
+ return Socket.startConnect(proxyHost, proxyPort as int);
+ }
+ ..findProxy = (uri) => "PROXY localhost:${proxyServer.port}";
+ final response = await client.getUrl(serverUri).then((request) {
+ return request.close();
+ });
+ Expect.equals(200, response.statusCode);
+ final responseText = await response
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello via Proxy", responseText);
+ client.close();
+ server.close();
+ proxyServer.shutdown();
+}
+
+testDifferentAddressFamiliesAndProxySettings(String dir) async {
+ // Test a custom connection factory for Unix domain sockets that also allows
+ // regular INET/INET6 access with and without a proxy.
+ var proxyServer = await setupProxyServer();
+ var inet6Server = await HttpServer.bind(InternetAddress.anyIPv6, 0);
+ inet6Server.forEach((HttpRequest request) {
+ request.response.write('Hello via Proxy');
+ request.response.close();
+ });
+ final inet6ServerUri = Uri.http("127.0.0.1:${inet6Server.port}", "/");
+ final unixPath = '$dir/sock';
+ final unixAddress = InternetAddress(unixPath, type: InternetAddressType.unix);
+ final unixServer = await HttpServer.bind(unixAddress, 0);
+ unixServer.forEach((HttpRequest request) {
+ request.response.write('Hello via Unix');
+ request.response.close();
+ });
+ final client = HttpClient()
+ ..connectionFactory = (uri, proxyHost, proxyPort) {
+ if (uri.scheme == 'unix') {
+ assert(proxyHost == null);
+ assert(proxyPort == null);
+ var address = InternetAddress(unixPath, type: InternetAddressType.unix);
+ return Socket.startConnect(address, 0);
+ } else {
+ if (proxyHost != null && proxyPort != null) {
+ return Socket.startConnect(proxyHost, proxyPort);
+ }
+ return Socket.startConnect(uri.host, uri.port);
+ }
+ }
+ ..findProxy = (uri) {
+ if (uri.scheme == 'unix') {
+ // Proxy settings are not meaningful for Unix domain sockets.
+ return 'DIRECT';
+ } else {
+ return "PROXY localhost:${proxyServer.port}";
+ }
+ };
+// Fetch a URL from the INET6 server and verify the results.
+ final inet6Response = await client.getUrl(inet6ServerUri).then((request) {
+ return request.close();
+ });
+ Expect.equals(200, inet6Response.statusCode);
+ final inet6ResponseText = await inet6Response
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello via Proxy", inet6ResponseText);
+// Fetch a URL from the Unix server and verify the results.
+ final unixResponse = await client
+ .getUrl(Uri(
+ scheme: "unix",
+ // Connection pooling is based on the host/port combination
+ // so ensure that the host is unique for unique logical
+ // endpoints. Also, the `host` property is converted to
+ // lowercase so you cannot use it directly for file paths.
+ host: 'dummy',
+ path: "/"))
+ .then((request) {
+ return request.close();
+ });
+ Expect.equals(200, unixResponse.statusCode);
+ final unixResponseText = await unixResponse
+ .transform(utf8.decoder)
+ .fold('', (String x, String y) => x + y);
+ Expect.equals("Hello via Unix", unixResponseText);
+ client.close();
+ inet6Server.close();
+ unixServer.close();
+ proxyServer.shutdown();
+}
+
+main() async {
+ await testDirectConnection();
+ await testConnectionViaProxy();
+ if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) {
+ await withTempDir('unix_socket_test', (Directory dir) async {
+ await testDifferentAddressFamiliesAndProxySettings('${dir.path}');
+ });
+ }
+}
diff --git a/tests/standalone_2/io/http_override_test.dart b/tests/standalone_2/io/http_override_test.dart
index 668263b..72b4967 100644
--- a/tests/standalone_2/io/http_override_test.dart
+++ b/tests/standalone_2/io/http_override_test.dart
@@ -39,6 +39,10 @@
set authenticate(Future<bool> f(Uri url, String scheme, String realm)) {}
void addCredentials(
Uri url, String realm, HttpClientCredentials credentials) {}
+ set connectionFactory(
+ Future<ConnectionTask<Socket>> Function(
+ Uri url, String proxyHost, int proxyPort)
+ f) {}
set findProxy(String f(Uri url)) {}
set authenticateProxy(
Future<bool> f(String host, int port, String scheme, String realm)) {}
@@ -79,6 +83,10 @@
set authenticate(Future<bool> f(Uri url, String scheme, String realm)) {}
void addCredentials(
Uri url, String realm, HttpClientCredentials credentials) {}
+ set connectionFactory(
+ Future<ConnectionTask<Socket>> Function(
+ Uri url, String proxyHost, int proxyPort)
+ f) {}
set findProxy(String f(Uri url)) {}
set authenticateProxy(
Future<bool> f(String host, int port, String scheme, String realm)) {}
diff --git a/tools/VERSION b/tools/VERSION
index 106f5a3..6a9c5f7 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 115
+PRERELEASE 116
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/dom/templates/html/impl/impl_Window.darttemplate b/tools/dom/templates/html/impl/impl_Window.darttemplate
index a433f83..9768d87 100644
--- a/tools/dom/templates/html/impl/impl_Window.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Window.darttemplate
@@ -198,7 +198,7 @@
* See [EventStreamProvider] for usage information.
*/
static const EventStreamProvider<BeforeUnloadEvent> beforeUnloadEvent =
- const _BeforeUnloadEventStreamProvider('beforeunload');
+ const EventStreamProvider('beforeunload');
/// Stream of `beforeunload` events handled by this [Window].
Stream<Event> get onBeforeUnload => beforeUnloadEvent.forTarget(this);
@@ -254,62 +254,3 @@
JS<num>('num', '#.scrollY', this).round() :
document.documentElement$NULLASSERT.scrollTop;
}
-
-class _BeforeUnloadEvent extends _WrappedEvent implements BeforeUnloadEvent {
- String _returnValue;
-
-$if NNBD
- _BeforeUnloadEvent(Event base)
- : _returnValue = '',
- super(base);
-$else
- _BeforeUnloadEvent(Event base) : super(base);
-$endif
-
- String get returnValue => _returnValue;
-
- set returnValue(String$NULLABLE value) {
- // Typed as nullable only to be compatible with the overriden method.
- _returnValue = value$NULLASSERT;
- // FF and IE use the value as the return value, Chrome will return this from
- // the event callback function.
- if (JS<bool>('bool', '("returnValue" in #)', wrapped)) {
- JS('void', '#.returnValue = #', wrapped, value);
- }
- }
-}
-
-class _BeforeUnloadEventStreamProvider implements
- EventStreamProvider<BeforeUnloadEvent> {
- final String _eventType;
-
- const _BeforeUnloadEventStreamProvider(this._eventType);
-
- Stream<BeforeUnloadEvent> forTarget(EventTarget$NULLABLE e, {bool useCapture: false}) {
- // Specify the generic type for EventStream only in dart2js.
- var stream = new _EventStream<BeforeUnloadEvent>(e, _eventType, useCapture);
- var controller = new StreamController<BeforeUnloadEvent>(sync: true);
-
- stream.listen((event) {
- var wrapped = new _BeforeUnloadEvent(event);
- controller.add(wrapped);
- });
-
- return controller.stream;
- }
-
- String getEventType(EventTarget target) {
- return _eventType;
- }
-
- ElementStream<BeforeUnloadEvent> forElement(Element e, {bool useCapture: false}) {
- // Specify the generic type for _ElementEventStreamImpl only in dart2js.
- return new _ElementEventStreamImpl<BeforeUnloadEvent>(e, _eventType, useCapture);
- }
-
- ElementStream<BeforeUnloadEvent> _forElementList(ElementList<Element> e,
- {bool useCapture: false}) {
- // Specify the generic type for _ElementEventStreamImpl only in dart2js.
- return new _ElementListEventStreamImpl<BeforeUnloadEvent>(e, _eventType, useCapture);
- }
-}