Files
sdk/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
Konstantin Shcheglov 3e73e2fed4 CQ. Fixes to make analyzer/ and analysis_server/ pass with TEST_ANALYZER_WINDOWS_PATHS=true on Mac.
Avoid constructing file URIs and paths directly from the host platform
when analyzer code is operating on an injected ResourceProvider. Those
direct conversions use the process path context, which is wrong when the
provider is using a Windows path context on another host platform.

Route file URI conversions through ResourceProvider, FileSource, File,
and PathContext APIs instead. This keeps file names, document links,
analysis options includes, navigation targets, and relative paths
aligned with the active provider.

Update affected tests and shared test utilities to use provider-backed
URIs and paths, so they exercise the same path semantics as production
code.

Change-Id: Iaf7751d028273a5fa72a04d8ca60e51aee700594
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499481
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
2026-04-30 08:20:28 -07:00

1699 lines
42 KiB
Dart

// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:test/test.dart';
import '../../tool/codebase/failing_tests.dart';
import '../lsp/request_helpers_mixin.dart';
import '../utils/test_code_extensions.dart';
import 'shared_test_interface.dart';
/// Shared editable arguments tests that are used by both LSP + Legacy server
/// tests.
mixin SharedEditableArgumentsTests
on SharedTestInterface, LspRequestHelpersMixin {
late TestCode code;
/// Initializes the server with [content] and fetches editable arguments.
Future<EditableArguments?> getEditableArgumentsFor(
String content, {
Future<void> Function(Uri, String)? open,
}) async {
// Default to the standart openFile function if we weren't overridden.
open ??= openFile;
code = TestCode.parse('''
import 'package:flutter/widgets.dart';
$content
''');
createFile(testFilePath, code.code);
await initializeServer();
await open(testFileUri, code.code);
await currentAnalysis;
return await getEditableArguments(testFileUri, code.position.position);
}
Matcher hasArg(Matcher matcher) {
return hasArgs(contains(matcher));
}
Matcher hasArgNamed(String argumentName, {String? doc}) {
return hasArg(isArg(argumentName));
}
Matcher hasArgs(Matcher matcher) {
return isA<EditableArguments>().having(
(arguments) => arguments.arguments,
'arguments',
matcher,
);
}
Matcher hasDocumentation(Matcher matcher) {
return isA<EditableArguments>().having(
(arguments) => arguments.documentation,
'documentation',
matcher,
);
}
Matcher hasName(Matcher matcher) {
return isA<EditableArguments>().having(
(arguments) => arguments.name,
'name',
matcher,
);
}
Matcher isArg(
String name, {
Object? documentation = anything,
Object? type = anything,
Object? value = anything,
Object? displayValue = anything,
Object? hasArgument = anything,
Object? defaultValue = anything,
Object? isRequired = anything,
Object? isNullable = anything,
Object? isDeprecated = anything,
Object? isEditable = anything,
Object? notEditableReason = anything,
Object? options = anything,
}) {
return isA<EditableArgument>()
.having((arg) => arg.name, 'name', name)
.having((arg) => arg.documentation, 'documentation', documentation)
.having((arg) => arg.type, 'type', type)
.having((arg) => arg.value, 'value', value)
.having((arg) => arg.displayValue, 'displayValue', displayValue)
.having((arg) => arg.hasArgument, 'hasArgument', hasArgument)
.having((arg) => arg.defaultValue, 'defaultValue', defaultValue)
.having((arg) => arg.isRequired, 'isRequired', isRequired)
.having((arg) => arg.isNullable, 'isNullable', isNullable)
.having((arg) => arg.isDeprecated, 'isDeprecated', isDeprecated)
.having((arg) => arg.isEditable, 'isEditable', isEditable)
.having(
(arg) => arg.notEditableReason,
'notEditableReason',
notEditableReason,
)
.having((arg) => arg.options, 'options', options)
// Some extra checks that should be true for all.
.having(
(arg) =>
arg.value == null ||
arg.value?.toString() != arg.displayValue?.toString(),
'different value and displayValues',
isTrue,
)
.having(
(arg) => (arg.notEditableReason == null) == arg.isEditable,
'notEditableReason must be supplied if isEditable=false',
isTrue,
)
.having(
(arg) => arg.value == null || arg.isEditable,
'isEditable must be true if there is a value',
isTrue,
)
.having(
(arg) => arg.type == 'enum'
? (arg.options?.isNotEmpty ?? false)
: arg.options == null,
'enum types must have options / non-enums must not have options',
isTrue,
);
}
Future<void> test_defaultValue_named_default() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget({int? a = 1});
@override
Widget build(BuildContext context) => MyW^idget(a: 1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: 1)));
}
Future<void> test_defaultValue_named_default_constantVariable() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
static const constantOne = 1;
const MyWidget({int? a = constantOne});
@override
Widget build(BuildContext context) => MyW^idget(a: 1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: 1)));
}
Future<void> test_defaultValue_named_default_null() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget({int? a = null});
@override
Widget build(BuildContext context) => MyW^idget(a: 1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: null)));
}
Future<void> test_defaultValue_named_noDefault() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget({int? a});
@override
Widget build(BuildContext context) => MyW^idget(a: 1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: null)));
}
Future<void> test_defaultValue_named_required_noDefault() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget({required int? a});
@override
Widget build(BuildContext context) => MyW^idget(a: 1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: null)));
}
Future<void> test_defaultValue_positional() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(int a);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: null)));
}
Future<void> test_defaultValue_positional_optional_default() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget([int? a = 1]);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: 1)));
}
Future<void> test_defaultValue_positional_optional_noDefault() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget([int? a]);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('a', defaultValue: null)));
}
Future<void> test_defaultValue_primaryConstructor() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget(int a, {int? b = 1}) extends StatelessWidget {
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('b', defaultValue: 1)));
}
Future<void> test_documentation_fieldParameter_literal() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
/// Documentation for x.
final int x;
/// Creates a MyWidget.
const MyWidget(this.x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('x', documentation: 'Documentation for x.')));
}
Future<void> test_documentation_fieldParameter_macro() async {
var result = await getEditableArgumentsFor('''
/// {@template shared_docs}
/// Shared docs.
/// {@endtemplate}
class MyWidget extends StatelessWidget {
/// {@macro shared_docs}
final int x;
/// Creates a MyWidget.
const MyWidget(this.x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('x', documentation: 'Shared docs.')));
}
Future<void> test_documentation_literal() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
/// Creates a MyWidget.
const MyWidget(int x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasDocumentation(equals('Creates a MyWidget.')));
}
Future<void> test_documentation_macro() async {
var result = await getEditableArgumentsFor('''
/// {@template my_widget_docs}
/// MyWidget shared docs.
/// {@endtemplate}
class MyWidget extends StatelessWidget {
/// {@macro my_widget_docs}
const MyWidget(int x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasDocumentation(equals('MyWidget shared docs.')));
}
Future<void> test_documentation_missing() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(int x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasDocumentation(isNull));
}
Future<void> test_documentation_widgetFactory() async {
var result = await getEditableArgumentsFor('''
extension on MyWidget {
/// Creates a Padded.
@widgetFactory
Widget padded(int x) => this;
}
class MyWidget extends StatelessWidget {
const MyWidget(int x);
@override
Widget build(BuildContext context) {
return padd^ed(1);
}
}
''');
expect(result, hasDocumentation(equals('Creates a Padded.')));
}
Future<void> test_hasArgument() async {
failTestOnErrorDiagnostic = false;
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(
int? aPositionalSupplied,
int? aPositionalNotSupplied, {
int? aNamedSupplied,
int? aNamedNotSupplied,
});
@override
Widget build(BuildContext context) => MyW^idget(1, aNamedSupplied: 1);
}
''');
expect(
result,
hasArgs(
unorderedEquals([
isArg('aPositionalSupplied', hasArgument: true),
isArg('aPositionalNotSupplied', hasArgument: false),
isArg('aNamedSupplied', hasArgument: true),
isArg('aNamedNotSupplied', hasArgument: false),
]),
),
);
}
Future<void> test_hasArgument_primaryConstructor() async {
failTestOnErrorDiagnostic = false;
var result = await getEditableArgumentsFor('''
class MyWidget(
int? aPositionalSupplied,
int? aPositionalNotSupplied, {
int? aNamedSupplied,
int? aNamedNotSupplied,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) => MyW^idget(1, aNamedSupplied: 1);
}
''');
expect(
result,
hasArgs(
unorderedEquals([
isArg('aPositionalSupplied', hasArgument: true),
isArg('aPositionalNotSupplied', hasArgument: false),
isArg('aNamedSupplied', hasArgument: true),
isArg('aNamedNotSupplied', hasArgument: false),
]),
),
);
}
Future<void> test_isDeprecated() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
/// Creates a MyWidget.
const MyWidget(
@deprecated
int aDeprecated,
int aNotDeprecated,
);
@override
Widget build(BuildContext context) => MyW^idget(1, 2);
}
''');
expect(
result,
hasArgs(
unorderedEquals([
isArg('aDeprecated', isDeprecated: true),
isArg('aNotDeprecated', isDeprecated: false),
]),
),
);
}
Future<void> test_isEditable_false_positional_optional() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget([int? a, int? b, int? c]);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('a', isEditable: true),
isArg('b', isEditable: true),
isArg(
'c',
// c is not editable because it is not guaranteed that we can insert
// a default value for b (it could be a private value or require
// imports).
isEditable: false,
notEditableReason:
"A value for the 3rd parameter can't be added until a value "
'for all preceding positional parameters have been added.',
),
]),
),
);
}
Future<void> test_isEditable_false_positional_required1() async {
failTestOnErrorDiagnostic = false;
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(int a, int b);
@override
Widget build(BuildContext context) => MyW^idget();
}
''');
expect(
result,
hasArg(
// b is not editable because there are missing required previous
// arguments (a).
isArg(
'b',
isEditable: false,
notEditableReason:
"A value for the 2nd parameter can't be added until a value "
'for all preceding positional parameters have been added.',
),
),
);
}
Future<void> test_isEditable_false_positional_required2() async {
failTestOnErrorDiagnostic = false;
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(int a, int b, int c);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(
result,
hasArg(
// c is not editable because there are missing required previous
// arguments (b).
isArg(
'c',
isEditable: false,
notEditableReason:
"A value for the 3rd parameter can't be added until a value "
'for all preceding positional parameters have been added.',
),
),
);
}
Future<void> test_isEditable_false_string_adjacent() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(String s);
@override
Widget build(BuildContext context) => MyW^idget('a' 'b');
}
''');
expect(
result,
hasArg(
isArg(
's',
type: 'string',
value: isNull,
displayValue: 'ab',
hasArgument: true,
isEditable: false,
notEditableReason: "Adjacent strings can't be edited",
),
),
);
}
Future<void> test_isEditable_false_string_interpolated() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(String s);
@override
Widget build(BuildContext context) => MyW^idget('${context.runtimeType}');
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg(
's',
type: 'string',
value: isNull,
displayValue: r"'${context.runtimeType}'",
isEditable: false,
notEditableReason: "Interpolated strings can't be edited",
),
]),
),
);
}
Future<void> test_isEditable_false_string_withNewlines() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(String sEscaped, String sLiteral);
@override
Widget build(BuildContext context) => MyW^idget(
'a\nb',
"""
a
b
""",
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg(
'sEscaped',
type: 'string',
value: isNull,
displayValue: 'a\nb',
isEditable: false,
notEditableReason: "Strings containing newlines can't be edited",
),
isArg(
'sLiteral',
type: 'string',
value: isNull,
displayValue: 'a\nb\n',
isEditable: false,
notEditableReason: "Strings containing newlines can't be edited",
),
]),
),
);
}
Future<void> test_isEditable_true_named() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget({int? a, int? b, int? c});
@override
Widget build(BuildContext context) => MyW^idget(a: 1);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('a', isEditable: true),
isArg('b', isEditable: true),
isArg('c', isEditable: true),
]),
),
);
}
Future<void> test_isEditable_true_positional_required() async {
failTestOnErrorDiagnostic = false;
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(int a, int b);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(
result,
hasArg(
isArg(
'b',
// b is editable because it's the next argument and we don't need
// to add anything additional.
isEditable: true,
),
),
);
}
Future<void> test_isEditable_true_string_dollar_escaped() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(String s);
@override
Widget build(BuildContext context) => MyW^idget('\${1}');
}
''');
expect(
result,
hasArg(
isArg(
's',
type: 'string',
value: r'${1}',
displayValue: isNull,
isEditable: true,
),
),
);
}
Future<void> test_isEditable_true_string_dollar_raw() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(String s);
@override
Widget build(BuildContext context) => MyW^idget(r'${1}');
}
''');
expect(
result,
hasArg(
isArg(
's',
type: 'string',
value: r'${1}',
displayValue: isNull,
isEditable: true,
),
),
);
}
Future<void>
test_isEditable_true_string_tripleQuoted_withoutNewlines() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget(String s);
@override
Widget build(BuildContext context) => MyW^idget("""string_value""");
}
''');
expect(
result,
hasArg(
isArg(
's',
type: 'string',
value: 'string_value',
displayValue: isNull,
isEditable: true,
),
),
);
}
Future<void> test_isNullable() async {
failTestOnErrorDiagnostic = false;
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(
int aPositional,
int? aPositionalNullable, {
int? aNamed,
required int aRequiredNamed,
required int? aRequiredNamedNullable
});
@override
Widget build(BuildContext context) => MyW^idget();
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('aPositional', isNullable: false),
isArg('aPositionalNullable', isNullable: true),
isArg('aNamed', isNullable: true),
isArg('aRequiredNamed', isNullable: false),
isArg('aRequiredNamedNullable', isNullable: true),
]),
),
);
}
Future<void> test_isRequired() async {
failTestOnErrorDiagnostic = false;
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(
int aPositional,
int? aPositionalNullable, {
int? aNamed,
required int aRequiredNamed,
required int? aRequiredNamedNullable
});
@override
Widget build(BuildContext context) => MyW^idget();
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('aPositional', isRequired: true),
isArg('aPositionalNullable', isRequired: true),
isArg('aNamed', isRequired: false),
isArg('aRequiredNamed', isRequired: true),
isArg('aRequiredNamedNullable', isRequired: true),
]),
),
);
}
Future<void> test_location_bad_extensionMethod_noWidgetFactory() async {
var result = await getEditableArgumentsFor('''
extension on MyWidget {
Widget padded(String a1) => this;
}
class MyWidget extends StatelessWidget {
const MyWidget();
const MyWidget.foo(String a1);
@override
Widget build(BuildContext context) => this.pad^ded('value1');
}
''');
expect(result, isNull);
}
Future<void> test_location_bad_functionInvocation() async {
var result = await getEditableArgumentsFor('''
MyWidget create(String a1) => throw '';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) => crea^te('value1');
}
''');
expect(result, isNull);
}
Future<void> test_location_bad_methodInvocation() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
@widgetFactory
MyWidget create(String a1) => throw '';
@override
Widget build(BuildContext context) => crea^te('value1');
}
''');
expect(result, isNull);
}
Future<void> test_location_bad_nonDart() async {
var textFilePath = pathContext.join(projectFolderPath, 'lib', 'test.txt');
var textFileUri = toUri(textFilePath);
var content = 'my text';
createFile(textFilePath, content);
await initializeServer();
await openFile(textFileUri, content);
await currentAnalysis;
var result = await getEditableArguments(
textFileUri,
Position(line: 0, character: 0),
);
expect(result, isNull);
}
Future<void> test_location_bad_unnamedConstructor_notWidget() async {
var result = await getEditableArgumentsFor('''
class MyWidget {
const MyWidget(String a1);
@override
MyWidget build(BuildContext context) => MyW^idget('value1');
}
''');
expect(result, isNull);
}
Future<void> test_location_good_argumentList_argumentName() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({required String a1 });
@override
Widget build(BuildContext context) => MyWidget(a^1: 'value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_argumentList_literalValue() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({required String a1 });
@override
Widget build(BuildContext context) => MyWidget(a1: 'val^ue1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_argumentList_nestedInvocation() async {
var result = await getEditableArgumentsFor('''
String getString() => '';
class MyWidget extends StatelessWidget {
const MyWidget(String a1);
@override
Widget build(BuildContext context) => MyWidget(getS^tring());
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void>
test_location_good_argumentList_nestedInvocation_arguments() async {
var result = await getEditableArgumentsFor('''
String getString(String s) => s;
class MyWidget extends StatelessWidget {
const MyWidget(String a1);
@override
Widget build(BuildContext context) => MyWidget(getString('valu^e1'));
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_argumentList_parens_afterOpen() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({required String a1 });
@override
Widget build(BuildContext context) => MyWidget(^a1: 'value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_argumentList_parens_beforeClose() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({required String a1 });
@override
Widget build(BuildContext context) => MyWidget(a1: 'value1'^);
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_argumentList_parens_beforeOpen() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({required String a1 });
@override
Widget build(BuildContext context) => MyWidget^(a1: 'value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_extensionMethod_constructorTarget() async {
var result = await getEditableArgumentsFor('''
extension on MyWidget {
@widgetFactory
Widget padded(String a1) => this;
}
class MyWidget extends StatelessWidget {
const MyWidget();
const MyWidget.foo(String a1);
@override
Widget build(BuildContext context) => MyWidget().pad^ded('value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_extensionMethod_thisTarget() async {
var result = await getEditableArgumentsFor('''
extension on MyWidget {
@widgetFactory
Widget padded(String a1) => this;
}
class MyWidget extends StatelessWidget {
const MyWidget.foo(String a1);
@override
Widget build(BuildContext context) => this.pad^ded('value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_extensionMethod_variableTarget() async {
var result = await getEditableArgumentsFor('''
extension on MyWidget {
@widgetFactory
Widget padded(String a1) => this;
}
class MyWidget extends StatelessWidget {
const MyWidget();
const MyWidget.foo(String a1);
@override
Widget build(BuildContext context) {
MyWidget? foo;
return foo!.pad^ded('value1');
}
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_namedConstructor_className() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget.foo(String a1);
@override
Widget build(BuildContext context) => MyW^idget.foo('value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_namedConstructor_constructorName() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget.foo(String a1);
@override
Widget build(BuildContext context) => MyWidget.f^oo('value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_location_good_unnamedConstructor() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(String a1);
@override
Widget build(BuildContext context) => MyW^idget('value1');
}
''');
expect(result, hasArgNamed('a1'));
}
Future<void> test_name_widgetConstructor() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(int x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasName(equals('MyWidget')));
}
Future<void> test_name_widgetConstructor_named() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget.named(int x);
@override
Widget build(BuildContext context) => MyW^idget.named(1);
}
''');
expect(result, hasName(equals('MyWidget')));
}
Future<void> test_name_widgetFactory() async {
var result = await getEditableArgumentsFor('''
extension on MyWidget {
@widgetFactory
Widget padded(int x) => this;
}
class MyWidget extends StatelessWidget {
const MyWidget(int x);
@override
Widget build(BuildContext context) {
return padd^ed(1);
}
}
''');
expect(result, hasName(isNull));
}
/// Arguments should be returned in the order of the parameters in the source
/// code. This keeps things consistent across different instances of the same
/// Widget class and prevents the order from changing as a user adds/removes
/// arguments.
///
/// If an editor wants to sort provided arguments first (and keep these stable
/// across add/removes) it could still do so client-side, whereas if server
/// orders them that way, the opposite (using source-order) is not possible.
Future<void> test_order() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({
int c1 = 0,
int c2 = 0,
int a1 = 0,
int a2 = 0,
int b1 = 0,
int b2 = 0,
});
@override
Widget build(BuildContext context) => MyW^idget(b1: 1, a1: 1, c1: 1);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('c1'),
isArg('c2'),
isArg('a1'),
isArg('a2'),
isArg('b1'),
isArg('b2'),
]),
),
);
}
Future<void> test_range() async {
var result = await getEditableArgumentsFor(r'''
class MyWidget extends StatelessWidget {
const MyWidget({int? a = null});
@override
Widget build(BuildContext context) => [!MyW^idget(a: 1)!];
}
''');
expect(result!.range, code.range.range);
}
Future<void> test_textDocument_unopenedFile() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(String a1);
@override
Widget build(BuildContext context) => MyW^idget('value1');
}
''', open: (_, _) async {});
// Verify null version for unopened file.
expect(
result!.textDocument,
isA<OptionalVersionedTextDocumentIdentifier>()
.having((td) => td.uri, 'uri', testFileUri)
.having((td) => td.version, 'version', null),
);
}
Future<void> test_textDocument_versioned() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(String a1);
@override
Widget build(BuildContext context) => MyW^idget('value1');
}
''');
// Verify initial content of 1.
expect(
result!.textDocument,
isA<VersionedTextDocumentIdentifier>()
.having((td) => td.uri, 'uri', testFileUri)
.having((td) => td.version, 'version', 1),
);
// Update the content to v5.
await replaceFile(5, testFileUri, '${code.code}\n// extra comment');
// Verify new results have version 5.
result = await getEditableArguments(testFileUri, code.position.position);
expect(
result!.textDocument,
isA<VersionedTextDocumentIdentifier>()
.having((td) => td.uri, 'uri', testFileUri)
.having((td) => td.version, 'version', 5),
);
}
Future<void> test_textDocument_versioned_closedFile() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget(String a1);
@override
Widget build(BuildContext context) => MyW^idget('value1');
}
''');
// Verify initial content of 1.
expect(
result!.textDocument,
isA<VersionedTextDocumentIdentifier>()
.having((td) => td.uri, 'uri', testFileUri)
.having((td) => td.version, 'version', 1),
);
// Close the file.
await closeFile(testFileUri);
// Verify new results have null version.
result = await getEditableArguments(testFileUri, code.position.position);
expect(
result!.textDocument,
isA<OptionalVersionedTextDocumentIdentifier>()
.having((td) => td.uri, 'uri', testFileUri)
.having((td) => td.version, 'version', isNull),
);
}
Future<void> test_type_bool() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({
bool supplied = true,
bool suppliedAsDefault = true,
bool notSupplied = true,
});
@override
Widget build(BuildContext context) => MyW^idget(
supplied: false,
suppliedAsDefault: true,
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg(
'supplied',
type: 'bool',
value: false,
hasArgument: true,
defaultValue: true,
),
isArg(
'suppliedAsDefault',
type: 'bool',
value: true,
hasArgument: true,
defaultValue: true,
),
isArg(
'notSupplied',
type: 'bool',
value: isNull,
hasArgument: false,
defaultValue: true,
),
]),
),
);
}
Future<void> test_type_bool_nonLiterals() async {
var result = await getEditableArgumentsFor('''
var myVar = true;
const myConst = true;
class MyWidget extends StatelessWidget {
const MyWidget({
bool? aVar,
bool? aConst,
bool? aExpr,
bool? aConstExpr,
});
@override
Widget build(BuildContext context) => MyW^idget(
aVar: myVar,
aConst: myConst,
aExpr: DateTime.now().isBefore(DateTime.now()),
aConstExpr: 1 == 2,
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('aVar', type: 'bool', value: isNull, displayValue: 'myVar'),
isArg('aConst', type: 'bool', value: true, displayValue: 'myConst'),
isArg(
'aExpr',
type: 'bool',
value: isNull,
displayValue: 'DateTime.now().isBefore(DateTime.now())',
),
isArg(
'aConstExpr',
type: 'bool',
value: false,
displayValue: '1 == 2',
),
]),
),
);
}
Future<void> test_type_double() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({
double supplied = 1.0,
double suppliedAsDefault = 1.0,
double notSupplied = 1.0,
});
@override
Widget build(BuildContext context) => MyW^idget(
supplied: 2.0,
suppliedAsDefault: 1.0,
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg(
'supplied',
type: 'double',
value: 2.0,
hasArgument: true,
defaultValue: 1.0,
),
isArg(
'suppliedAsDefault',
type: 'double',
value: 1.0,
hasArgument: true,
defaultValue: 1.0,
),
isArg(
'notSupplied',
type: 'double',
value: isNull,
hasArgument: false,
defaultValue: 1.0,
),
]),
),
);
}
Future<void> test_type_double_intLiterals() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({
double supplied = 1,
double suppliedAsDefault = 1,
double notSupplied = 1,
});
@override
Widget build(BuildContext context) => MyW^idget(
supplied: 2,
suppliedAsDefault: 1,
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg(
'supplied',
type: 'double',
value: 2,
hasArgument: true,
defaultValue: 1,
),
isArg(
'suppliedAsDefault',
type: 'double',
value: 1,
hasArgument: true,
defaultValue: 1,
),
isArg(
'notSupplied',
type: 'double',
value: isNull,
hasArgument: false,
defaultValue: 1,
),
]),
),
);
}
Future<void> test_type_double_nonLiterals() async {
var result = await getEditableArgumentsFor('''
var myVar = 1.0;
const myConst = 1.0;
class MyWidget extends StatelessWidget {
const MyWidget({
double? aVar,
double? aConst,
double? aExpr,
double? aConstExpr,
});
@override
Widget build(BuildContext context) => MyW^idget(
aVar: myVar,
aConst: myConst,
aExpr: DateTime.now().millisecondsSinceEpoch.toDouble(),
aConstExpr: 1.0 + myConst,
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('aVar', type: 'double', value: isNull, displayValue: 'myVar'),
isArg('aConst', type: 'double', value: 1.0, displayValue: 'myConst'),
isArg(
'aExpr',
type: 'double',
value: isNull,
displayValue: 'DateTime.now().millisecondsSinceEpoch.toDouble()',
),
isArg(
'aConstExpr',
type: 'double',
value: 2.0,
displayValue: '1.0 + myConst',
),
]),
),
);
}
Future<void> test_type_enum() async {
var result = await getEditableArgumentsFor('''
enum E { one, two }
class MyWidget extends StatelessWidget {
const MyWidget({
E supplied = E.one,
E suppliedAsDefault = E.one,
E notSupplied = E.one,
});
@override
Widget build(BuildContext context) => MyW^idget(
supplied: E.two,
suppliedAsDefault: E.one,
);
}
''');
var optionsMatcher = equals(['E.one', 'E.two']);
expect(
result,
hasArgs(
orderedEquals([
isArg(
'supplied',
type: 'enum',
value: 'E.two',
hasArgument: true,
defaultValue: 'E.one',
options: optionsMatcher,
),
isArg(
'suppliedAsDefault',
type: 'enum',
value: 'E.one',
hasArgument: true,
defaultValue: 'E.one',
options: optionsMatcher,
),
isArg(
'notSupplied',
type: 'enum',
value: isNull,
hasArgument: false,
defaultValue: 'E.one',
options: optionsMatcher,
),
]),
),
);
}
Future<void> test_type_enum_nonLiterals() async {
var result = await getEditableArgumentsFor('''
enum E { one, two }
var myVar = E.one;
const myConst = E.one;
class MyWidget extends StatelessWidget {
const MyWidget({
E? aVar,
E? aConst,
E? aExpr,
});
@override
Widget build(BuildContext context) => MyW^idget(
aVar: myVar,
aConst: myConst,
aExpr: E.values.first,
);
}
''');
var optionsMatcher = equals(['E.one', 'E.two']);
expect(
result,
hasArgs(
orderedEquals([
isArg(
'aVar',
type: 'enum',
value: isNull,
displayValue: 'myVar',
options: optionsMatcher,
),
isArg(
'aConst',
type: 'enum',
value: 'E.one',
displayValue: 'myConst',
options: optionsMatcher,
),
isArg(
'aExpr',
type: 'enum',
value: isNull,
displayValue: 'E.values.first',
options: optionsMatcher,
),
]),
),
);
}
Future<void> test_type_int() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({
int supplied = 1,
int suppliedAsDefault = 1,
int notSupplied = 1,
});
@override
Widget build(BuildContext context) => MyW^idget(
supplied: 2,
suppliedAsDefault: 1,
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg(
'supplied',
type: 'int',
value: 2,
hasArgument: true,
defaultValue: 1,
),
isArg(
'suppliedAsDefault',
type: 'int',
value: 1,
hasArgument: true,
defaultValue: 1,
),
isArg(
'notSupplied',
type: 'int',
value: isNull,
hasArgument: false,
defaultValue: 1,
),
]),
),
);
}
Future<void> test_type_int_nonLiterals() async {
var result = await getEditableArgumentsFor('''
var myVar = 1;
const myConst = 1;
class MyWidget extends StatelessWidget {
const MyWidget({
int? aVar,
int? aConst,
int? aExpr,
int? aConstExpr,
});
@override
Widget build(BuildContext context) => MyW^idget(
aVar: myVar,
aConst: myConst,
aExpr: DateTime.now().millisecondsSinceEpoch,
aConstExpr: 1 + myConst,
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('aVar', type: 'int', value: isNull, displayValue: 'myVar'),
isArg('aConst', type: 'int', value: 1, displayValue: 'myConst'),
isArg(
'aExpr',
type: 'int',
value: isNull,
displayValue: 'DateTime.now().millisecondsSinceEpoch',
),
isArg(
'aConstExpr',
type: 'int',
value: 2,
displayValue: '1 + myConst',
),
]),
),
);
}
Future<void> test_type_string() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
const MyWidget({
String supplied = 'a',
String suppliedAsDefault = 'a',
String notSupplied = 'a',
});
@override
Widget build(BuildContext context) => MyW^idget(
supplied: 'b',
suppliedAsDefault: 'a',
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg(
'supplied',
type: 'string',
value: 'b',
hasArgument: true,
defaultValue: 'a',
),
isArg(
'suppliedAsDefault',
type: 'string',
value: 'a',
hasArgument: true,
defaultValue: 'a',
),
isArg(
'notSupplied',
type: 'string',
value: isNull,
hasArgument: false,
defaultValue: 'a',
),
]),
),
);
}
Future<void> test_type_string_nonLiterals() async {
var result = await getEditableArgumentsFor('''
var myVar = 'a';
const myConst = 'a';
class MyWidget extends StatelessWidget {
const MyWidget({
String? aVar,
String? aConst,
String? aExpr,
String? aConstExpr,
});
@override
Widget build(BuildContext context) => MyW^idget(
aVar: myVar,
aConst: myConst,
aExpr: DateTime.now().toString(),
aConstExpr: 'a' + 'b',
);
}
''');
expect(
result,
hasArgs(
orderedEquals([
isArg('aVar', type: 'string', value: isNull, displayValue: 'myVar'),
isArg('aConst', type: 'string', value: 'a', displayValue: 'myConst'),
isArg(
'aExpr',
type: 'string',
value: isNull,
displayValue: 'DateTime.now().toString()',
),
isArg(
'aConstExpr',
type: 'string',
value: 'ab',
displayValue: "'a' + 'b'",
),
]),
),
);
}
}