| // 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_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_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 = Uri.file(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'", |
| ), |
| ]), |
| ), |
| ); |
| } |
| } |