Version 3.9.0-328.0.dev Merge 1afbc518744af66c854086351322524225eb9fc3 into dev
diff --git a/DEPS b/DEPS index 777926e..1803e30 100644 --- a/DEPS +++ b/DEPS
@@ -140,7 +140,7 @@ "i18n_rev": "42c49328f8c040b663c2a2d94be9b6ddd80a70bd", "leak_tracker_rev": "f5620600a5ce1c44f65ddaa02001e200b096e14c", # rolled manually "material_color_utilities_rev": "799b6ba2f3f1c28c67cc7e0b4f18e0c7d7f3c03e", - "native_rev": "7944c5a0d26f3948273096239c9c2a8d6f0cda54", # rolled manually while native assets are experimental + "native_rev": "723cd56a5edc89699db32bca1b5bf91aa0bcf0cf", # rolled manually while native assets are experimental "protobuf_rev": "bce362dec347ae3cca085bd28db2aa2649f754e5", "pub_rev": "f6457fd20c9a9734a747262b1bcc2200c8357efb", # rolled manually "shelf_rev": "082d3ac2d13a98700d8148e8fad8f3e12a6fd0e1",
diff --git a/pkg/_fe_analyzer_shared/lib/src/base/errors.dart b/pkg/_fe_analyzer_shared/lib/src/base/errors.dart index dae3828..5dc0d5a 100644 --- a/pkg/_fe_analyzer_shared/lib/src/base/errors.dart +++ b/pkg/_fe_analyzer_shared/lib/src/base/errors.dart
@@ -32,7 +32,7 @@ @AnalyzerPublicApi(message: 'exported by package:analyzer/error/error.dart') abstract class DiagnosticCode { /// Regular expression for identifying positional arguments in error messages. - static final RegExp _positionalArgumentRegExp = new RegExp(r'{(\d+)\}'); + static final RegExp _positionalArgumentRegExp = new RegExp(r'\{(\d+)\}'); /** * The name of the error code.
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/fix_data_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/fix_data_generator.dart index 39ad136..c59cbf8 100644 --- a/pkg/analysis_server/lib/src/services/completion/yaml/fix_data_generator.dart +++ b/pkg/analysis_server/lib/src/services/completion/yaml/fix_data_generator.dart
@@ -65,6 +65,7 @@ 'constructor': EmptyProducer(), 'enum': EmptyProducer(), 'extension': EmptyProducer(), + 'extensionType': EmptyProducer(), 'field': EmptyProducer(), 'function': EmptyProducer(), 'getter': EmptyProducer(), @@ -76,6 +77,7 @@ 'inClass': EmptyProducer(), 'inEnum': EmptyProducer(), 'inExtension': EmptyProducer(), + 'inExtensionType': EmptyProducer(), 'inMixin': EmptyProducer(), });
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_descriptor.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_descriptor.dart index 7ace4c3..cf216b6 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_descriptor.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_descriptor.dart
@@ -47,19 +47,16 @@ // TODO(brianwilkerson): Check the resolved element, if one exists, for more // accurate results. return switch (kind) { - ElementKind.classKind => - // TODO(brianwilkerson): Handle this case. - false, + ElementKind.classKind => _matchesType(node), ElementKind.constantKind => // TODO(brianwilkerson): Handle this case. false, ElementKind.constructorKind => _matchesConstructor(node), - ElementKind.enumKind => - // TODO(brianwilkerson): Handle this case. - false, + ElementKind.enumKind => _matchesType(node), ElementKind.extensionKind => // TODO(brianwilkerson): Handle this case. false, + ElementKind.extensionTypeKind => _matchesType(node), ElementKind.fieldKind => // TODO(brianwilkerson): Handle this case. false, @@ -68,15 +65,11 @@ // TODO(brianwilkerson): Handle this case. false, ElementKind.methodKind => _matchesMethod(node), - ElementKind.mixinKind => - // TODO(brianwilkerson): Handle this case. - false, + ElementKind.mixinKind => _matchesType(node), ElementKind.setterKind => // TODO(brianwilkerson): Handle this case. false, - ElementKind.typedefKind => - // TODO(brianwilkerson): Handle this case. - false, + ElementKind.typedefKind => _matchesType(node), ElementKind.variableKind => // TODO(brianwilkerson): Handle this case. false, @@ -167,6 +160,22 @@ return false; } + bool _matchesType(AstNode node) { + if (components.length > 1) { + return false; + } + var name = components[0]; + if (node is Identifier) { + // A plain or prefixed identifier with the correct name. + var typeName = _nameFromIdentifier(node); + return name == typeName; + } + if (node is NamedType) { + return name == node.name.lexeme; + } + return false; + } + String _nameFromIdentifier(Identifier identifier) { if (identifier is SimpleIdentifier) { return identifier.name;
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_kind.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_kind.dart index 6479a1c..4511470 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_kind.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_kind.dart
@@ -4,45 +4,29 @@ /// An indication of the kind of an element. enum ElementKind { - classKind, - constantKind, - constructorKind, - enumKind, - extensionKind, - fieldKind, - functionKind, - getterKind, - methodKind, - mixinKind, - setterKind, - typedefKind, - variableKind, -} + classKind('class'), + constantKind('constant'), + constructorKind('constructor'), + enumKind('enum'), + extensionKind('extension'), + extensionTypeKind('extensionType'), + fieldKind('field'), + functionKind('function'), + getterKind('getter'), + methodKind('method'), + mixinKind('mixin'), + setterKind('setter'), + typedefKind('typedef'), + variableKind('variable'); -extension ElementKindUtilities on ElementKind { - /// Return a human readable name for the kind. - String get displayName { - return switch (this) { - ElementKind.classKind => 'class', - ElementKind.constantKind => 'constant', - ElementKind.constructorKind => 'constructor', - ElementKind.enumKind => 'enum', - ElementKind.extensionKind => 'extension', - ElementKind.fieldKind => 'field', - ElementKind.functionKind => 'function', - ElementKind.getterKind => 'getter', - ElementKind.methodKind => 'method', - ElementKind.mixinKind => 'mixin', - ElementKind.setterKind => 'setter', - ElementKind.typedefKind => 'typedef', - ElementKind.variableKind => 'variable', - }; - } + /// A human readable name for the kind. + final String displayName; + const ElementKind(this.displayName); - /// Return the element kind corresponding to the given [name]. + /// The element kind corresponding to the given [name]. static ElementKind? fromName(String name) { - for (var kind in ElementKind.values) { - if (kind.toString() == 'ElementKind.${name}Kind') { + for (var kind in values) { + if (kind.displayName == name) { return kind; } }
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart index cd3f11e..67c2e86 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart
@@ -28,7 +28,7 @@ final List<String> components; /// A list of the kinds of elements that are appropriate for some given - /// location in the code An empty list represents all kinds rather than no + /// location in the code. An empty list represents all kinds rather than no /// kinds. final List<ElementKind> validKinds; @@ -291,12 +291,21 @@ // get a more exact matcher. // TODO(brianwilkerson): Use 'new' for the name of the unnamed constructor. var constructorName = node.name?.name ?? ''; // ?? 'new'; - var className = node.type.name.lexeme; + var typeName = node.type.name.lexeme; _addMatcher( - components: [constructorName, className], + components: [constructorName, typeName], kinds: const [ElementKind.constructorKind], ); - _addMatcher(components: [className], kinds: const [ElementKind.classKind]); + _addMatcher( + components: [typeName], + kinds: const [ + ElementKind.classKind, + ElementKind.enumKind, + ElementKind.extensionTypeKind, + ElementKind.typedefKind, + ElementKind.mixinKind, // Can't *yet* have factory constructors. + ], + ); } /// Build a matcher for the extension. @@ -369,10 +378,14 @@ kinds: [ ElementKind.classKind, ElementKind.constructorKind, + ElementKind.enumKind, ElementKind.extensionKind, + ElementKind.extensionTypeKind, ElementKind.functionKind, ElementKind.getterKind, ElementKind.methodKind, + ElementKind.mixinKind, + ElementKind.typedefKind, ], ); } @@ -391,6 +404,7 @@ kinds: const [ ElementKind.classKind, ElementKind.enumKind, + ElementKind.extensionTypeKind, ElementKind.mixinKind, ElementKind.typedefKind, ], @@ -413,20 +427,17 @@ // get a more exact matcher. var prefix = node.prefix; if (prefix.element is PrefixElement) { - var parent = node.parent; - if ((parent is NamedType && parent.parent is! ConstructorName) || - (parent is PropertyAccess && parent.target == node)) { - _addMatcher( - components: [node.identifier.name], - kinds: const [ - ElementKind.classKind, - ElementKind.enumKind, - ElementKind.extensionKind, - ElementKind.mixinKind, - ElementKind.typedefKind, - ], - ); - } + _addMatcher( + components: [node.identifier.name], + kinds: const [ + ElementKind.classKind, + ElementKind.enumKind, + ElementKind.extensionKind, + ElementKind.extensionTypeKind, + ElementKind.mixinKind, + ElementKind.typedefKind, + ], + ); _addMatcher( components: [node.identifier.name], kinds: const [
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/replaced_by.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/replaced_by.dart index 3622581..d724053 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/replaced_by.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/replaced_by.dart
@@ -43,33 +43,46 @@ // ignore: library_private_types_in_public_api void apply(DartFileEditBuilder builder, DataDrivenFix fix, _Data data) { var referenceRange = data.referenceRange; + ImportLibraryElementResult? importElement; + var libraryUris = newElement.libraryUris; + if (libraryUris.isNotEmpty) { + // A library URI from `libraryUris`, either one already imported, + // or the first one in the list. + var libraryUri = _selectImportUri(builder, libraryUris); + importElement = builder.importLibraryElement(libraryUri); + } builder.addReplacement(referenceRange, (builder) { - if (data.replacement != null) { - builder.write(data.replacement!); + var components = newElement.components; + if (data.isInstanceMember && !replaceTarget) { + // Just replace the member on the same type. + builder.write(components.first); } else { - var templateContext = TemplateContext(fix.node, fix.utils); - var components = newElement.components; + // Replaces a static access, including any prefix and static scope. + if (importElement?.prefix case var prefix?) { + builder + ..write(prefix) + ..write('.'); + } if (components[0].isEmpty) { + // An unnamed constructor, always directly inside top-level declaration. builder.write(components[1]); } else { builder.write(components.reversed.join('.')); } - if (arguments.isNotEmpty) { - builder.write('('); - arguments.first.writeOn(builder, templateContext); - for (int i = 1; i < arguments.length; i++) { - builder.write(','); - arguments[i].writeOn(builder, templateContext); - } - builder.write(')'); + } + if (arguments.isNotEmpty) { + var templateContext = TemplateContext(fix.node, fix.utils); + builder.write('('); + arguments.first.writeOn(builder, templateContext); + for (int i = 1; i < arguments.length; i++) { + builder.write(','); + arguments[i].writeOn(builder, templateContext); } + builder.write(')'); + } else if (data.suffix case var suffix?) { + builder.write(suffix); } }); - var libraryUris = newElement.libraryUris; - if (libraryUris.isEmpty) return; - if (!libraryUris.any((uri) => builder.importsLibrary(uri))) { - builder.importLibraryElement(libraryUris.first); - } } @override @@ -78,180 +91,213 @@ // ignore: library_private_types_in_public_api _Data? validate(DataDrivenFix fix) { var node = fix.node; - if (replaceTarget) { - // This does not work if the element to be replaced is cascaded. - var parent = node.parent; - if (parent != null) { - var target = switch (parent) { - MethodInvocation() => parent.target, - PropertyAccess() => parent.target, - PrefixedIdentifier() => parent, - _ => null, - }; - if (target == null || - // replacing method with getter is not allowed - (parent is MethodInvocation && - newElement.kind == ElementKind.getterKind)) { - return null; - } - return _Data(range.startEnd(target, node)); - } else { - return null; + // Replaces all targets of static accesses, including prefixes. + // If [replaceTarget] is `true`, also replace targets of instance + // accesses. + if (node is SimpleIdentifier) { + // Include prefix if prefixed. + var element = node.element; + if (element is ExecutableElement && + !element.isStatic && + element is! ConstructorElement) { + var result = _instanceInvocation(fix, node, element, replaceTarget); + return result; } } - if (node is SimpleIdentifier) { - var element = node.element; - if (element is ExecutableElement && !element.isStatic) { - return _instance(node, element); - } - - var components = fix.element.components; - if (components.isEmpty) { - return null; - } else if (components.length == 1) { - if (components[0] != node.name) { - return null; - } - // We have an '<element>' pattern, so we replace the name. - return _Data(range.node(node)); - } - // The element being replaced is a member in a top-level element. - var containerName = components[1]; - if (components[0].isEmpty && containerName == node.name) { - // We have a '<className>()' pattern, so we replace the class name. - return _Data(range.node(node)); - } - var parent = node.parent; - if (parent is MethodInvocation) { - var target = parent.target; - if (target == null) { - // We have a '<member>()' pattern, so we replace the member name. - return _Data(range.node(node)); - } else if (target is SimpleIdentifier && target.name == containerName) { - // We have a '<container>.<member>()' pattern, so we replace both parts. - return _Data(range.startEnd(target, node)); - } else if (target is PrefixedIdentifier) { - if (target.prefix.element is PrefixElement && - target.identifier.name == containerName) { - // We have a '<prefix>.<container>.<member>()' pattern so we leave - // the prefix while replacing the rest. - return _Data(range.startEnd(target.identifier, node)); - } - // We shouldn't get here. - return null; - } - } else if (parent is NamedType) { - var grandparent = parent.parent; - if (grandparent is ConstructorName && - grandparent.name?.name == components[0]) { - // TODO(brianwilkerson): This doesn't correctly handle constructor - // invocations with type arguments. We really need to replace the - // class and constructor names separately. - return _Data(range.node(grandparent)); - } - } else if (parent is PrefixedIdentifier) { - if (parent.prefix.element is PrefixElement) { - // We have a '<prefix>.<topLevel>' pattern so we leave the prefix - // while replacing the rest. - return _Data(range.node(node)); - } - // We have a '<container>.<member>' pattern so we replace both parts. - return _Data(range.node(parent)); - } else if (parent is PropertyAccess) { - var target = parent.target; - if (target is PrefixedIdentifier) { - // We have a '<prefix>.<container>.<member>' pattern so we leave the - // prefix while replacing the rest. - return _Data(range.startEnd(target.identifier, node)); - } - // We have a '<container>.<member>' pattern so we replace both parts. - return _Data(range.node(parent)); - } - // We have a '<member>' pattern so we replace the member name. - return _Data(range.node(node)); - } else if (node is PrefixedIdentifier) { - var parent = node.parent; - if (parent is NamedType) { - var identifier = node.identifier; - var components = fix.element.components; - if (components.length > 1 && - components[0].isEmpty && - components[1] == identifier.name) { - // We have a '<prefix>.<className>' pattern, so we replace only the - // class name. - return _Data(range.node(identifier)); - } - } - } else if (node is NamedType) { - var identifier = node.name; - var components = fix.element.components; - if (components.length > 1 && - components[0].isEmpty && - components[1] == identifier.lexeme) { - // We have a '<prefix>.<className>' pattern, so we replace only the - // class name. - return _Data(range.token(identifier)); - } - var parent = node.parent; - if (parent is ConstructorName) { - var classNameToken = parent.type.name; - var constructorNameNode = parent.name; - var constructorName = constructorNameNode?.name ?? ''; - var components = fix.element.components; - if (components.length == 2 && - constructorName == components[0] && - classNameToken.lexeme == components[1]) { - if (constructorNameNode != null) { - return _Data(range.startEnd(classNameToken, constructorNameNode)); - } - return _Data(range.token(classNameToken)); - } - } - } else if (node is ConstructorName) { - var classNameToken = node.type.name; - var constructorNameNode = node.name; - var constructorName = constructorNameNode?.name ?? ''; - var components = fix.element.components; - if (components.length == 2 && - constructorName == components[0] && - classNameToken.lexeme == components[1]) { - if (constructorNameNode != null) { - return _Data(range.startEnd(classNameToken, constructorNameNode)); - } - return _Data(range.token(classNameToken)); - } + var sourceRange = _rangeWithTarget(node, fix); + if (sourceRange != null) { + return _Data(sourceRange); } return null; } - /// Returns a replacement of an instance member. - _Data? _instance(AstNode node, ExecutableElement element) { - var newComponents = newElement.components; + /// Replacement range of a function, getter or setter invocation. + /// + /// If [replaceTarget] is true, the target is also replaced. + /// That should always be the case for static functions, and is true + /// for instance functions if the fix is set to replace the target. + /// + // TODO(brianwilkinson): Maybe also handle setters<->one parameter functions + // switching between `(target.)setter = e` and `(target.)method(e)`. + _Data? _instanceInvocation( + DataDrivenFix fix, + SimpleIdentifier node, + ExecutableElement element, + bool replaceTarget, + ) { var newKind = newElement.kind; - var suffix = ''; + String? suffix; SourceRange? referenceRange; if (newKind == ElementKind.methodKind && element is GetterElement) { + // Convert from getter to method with no type arguments or arguments. suffix = '()'; - referenceRange = range.node(node); + AstNode rangeStart; + if (replaceTarget) { + rangeStart = _simpleIdentifierRangeStartWithTarget(node); + } else { + rangeStart = node; + } + referenceRange = range.startEnd(rangeStart, node); } else if (newKind == ElementKind.getterKind) { + // Convert from function call to getter, + // only if no arguments or type arguments. var parent = node.parent; if (parent is MethodInvocation) { var argumentList = parent.argumentList; if (argumentList.arguments.isNotEmpty) { return null; } - referenceRange = range.startEnd(node, argumentList); + var argumentTypes = parent.typeArgumentTypes; + if (argumentTypes != null && argumentTypes.isNotEmpty) { + return null; + } + if (replaceTarget) { + var rangeStart = _simpleIdentifierRangeStartWithTarget(node); + referenceRange = range.startEnd(rangeStart, argumentList); + } else { + referenceRange = range.startEnd(node, argumentList); + } } } - if (referenceRange == null) return null; - return _Data(referenceRange, replacement: '${newComponents[0]}$suffix'); + + if (referenceRange == null) { + if (replaceTarget) { + var rangeStart = _simpleIdentifierRangeStartWithTarget(node); + referenceRange = range.startEnd(rangeStart, node); + } else { + return null; + } + } + return _Data(referenceRange, suffix: suffix, isInstanceMember: true); + } + + /// Finds a range for a node that includes its target. + /// + /// For a [NamedType], it does not include type arguments or `?`. + SourceRange? _rangeWithTarget(AstNode node, DataDrivenFix fix) { + var elements = fix.element.components; + if (elements.isEmpty) return null; + var simpleName = elements[0]; + if (node is SimpleIdentifier) { + if (node.name != (simpleName.isNotEmpty ? simpleName : elements[1])) { + return null; + } + var rangeStart = _simpleIdentifierRangeStartWithTarget(node); + return range.startEnd(rangeStart, node); + } else if (node is PrefixedIdentifier) { + return range.startEnd(node.prefix, node); + } else if (node is ConstructorName) { + // TODO(brianwilkinson): Remember the type arguments, so they can be retained when + // replacing a named constructor, or placed correctly if replacing with + // a named constructor. + if (node.name != null) { + return range.node(node); + } else { + // Return the type's names only (omit/retain type arguments). + return range.node(node.type); + } + } else if (node is NamedType) { + var typeName = node.name.lexeme; + if (elements.length == 1 && simpleName == typeName || + elements.length == 2 && + simpleName.isEmpty && + elements[1] == typeName) { + // Omit trailing type parameters and/or `?`, + // only include import prefix and type name. + return range.startEnd(node, node.name); + } else { + // Check if it's a static member or constructor of a deprecated type. + if (elements.length == 2 && elements[1] == typeName) { + // And elements[0] is not empty. + + // Static accesses on a deprecated type. + var parent = node.parent; + if (parent is ConstructorName && parent.name?.name == simpleName) { + // TODO(brianwilkinson): Retain type arguments. + return range.node(parent); + } + if (parent is MethodInvocation && + parent.methodName.name == simpleName) { + return range.startEnd(node, parent.methodName); + } + if (parent is PropertyAccess && + parent.propertyName.name == simpleName) { + return range.startEnd(node, parent.propertyName); + } + } + } + } + return null; + } + + /// The start of any target of a static name, or the name itself. + /// + /// If the name has a static target or prefix, it should be included in the + /// range, otherwise just start at the node itself. + /// This checks any parent node which can contain a [SimpleIdentifier], + /// where that identifier can be the target of a static access, + /// and it then includes all prior tokens of the expression. + /// Some nodes may contain more than one [SimpleIdentifier], + /// but they can all be handled by finding the start of the expression. + /// (Can't just use the start of the parent node, since that can include + /// metadata.) + /// + AstNode _simpleIdentifierRangeStartWithTarget(SimpleIdentifier node) { + var parent = node.parent; + if (parent is MethodInvocation) { + // Correct whether `node` is `parent.target` or `parent.methodName`. + // The `target` can be `null` if the access is a cascade selector. + // In that case, we cannot replace the target. + // Also, `parent.operator` must be `.` for a static access. + return parent.target ?? node; + } else if (parent is PropertyAccess) { + // Correct whether `node` is `parent.target` or `parent.propertyName`. + // The `target` can be `null` if the access is a cascade selector. + // Also, `parent.operator` must be `.` for a static access. + return parent.target ?? node; + } else if (parent is PrefixedIdentifier) { + // Correct whether `node` is `parent.prefix` or `parent.identifier`. + return parent.prefix; + } else if (parent is ConstructorName) { + // Correct whether `node` is `parent.type` or `parent.name`. + // TODO(brianwilkinson): When replacing a named constructor, retain the type arguments. + return parent.type; + } else if (parent is Annotation) { + // Correct whether `node` is `parent.prefix` or `parent.constructorName`. + return parent.name; + } else if (parent is DotShorthandConstructorInvocation || + parent is DotShorthandInvocation || + parent is DotShorthandPropertyAccess) { + // Will insert a non-dot-shorthand reference to the new value. + return parent!; + } + // Nothing before the node. + return node; + } + + // Find the import element of `libraryUri`. + static Uri _selectImportUri( + DartFileEditBuilder builder, + List<Uri> libraryUris, + ) { + assert(libraryUris.isNotEmpty); + for (var uri in libraryUris) { + if (builder.importsLibrary(uri)) return uri; + } + return libraryUris.first; } } /// The data about a reference to an element that's been replaced. class _Data { final SourceRange referenceRange; - final String? replacement; - _Data(this.referenceRange, {this.replacement}); + /// Written after the reference, if not `null`. + /// + /// Used to add `()` when replacing a getter with a function. + final String? suffix; + + final bool isInstanceMember; + + _Data(this.referenceRange, {this.suffix, this.isInstanceMember = false}); }
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart index 386b5d7..068796f 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
@@ -57,6 +57,7 @@ static const String _expressionKey = 'expression'; static const String _extendsKey = 'extends'; static const String _extensionKey = 'extension'; + static const String _extensionTypeKey = 'extensionType'; static const String _fieldKey = 'field'; static const String _functionKey = 'function'; static const String _getterKey = 'getter'; @@ -64,6 +65,7 @@ static const String _inClassKey = 'inClass'; static const String _inEnumKey = 'inEnum'; static const String _inExtensionKey = 'inExtension'; + static const String _inExtensionTypeKey = 'inExtensionType'; static const String _indexKey = 'index'; static const String _inMixinKey = 'inMixin'; static const String _kindKey = 'kind'; @@ -92,12 +94,27 @@ /// A table mapping top-level keys for member elements to the list of keys for /// the possible containers of that element. static const Map<String, Set<String>> _containerKeyMap = { - _constructorKey: {_inClassKey}, + _constructorKey: {_inClassKey, _inExtensionTypeKey}, _constantKey: {_inEnumKey}, - _fieldKey: {_inClassKey, _inExtensionKey, _inMixinKey}, - _getterKey: {_inClassKey, _inExtensionKey, _inMixinKey}, - _methodKey: {_inClassKey, _inExtensionKey, _inMixinKey}, - _setterKey: {_inClassKey, _inExtensionKey, _inMixinKey}, + _fieldKey: {_inClassKey, _inExtensionKey, _inExtensionTypeKey, _inMixinKey}, + _getterKey: { + _inClassKey, + _inExtensionKey, + _inExtensionTypeKey, + _inMixinKey, + }, + _methodKey: { + _inClassKey, + _inExtensionKey, + _inExtensionTypeKey, + _inMixinKey, + }, + _setterKey: { + _inClassKey, + _inExtensionKey, + _inExtensionTypeKey, + _inMixinKey, + }, }; static const String _addParameterKind = 'addParameter'; @@ -844,12 +861,13 @@ var elementPair = _singleKey( map: node, translators: { - { + const { _classKey, _constantKey, _constructorKey, _enumKey, _extensionKey, + _extensionTypeKey, _fieldKey, _functionKey, _getterKey, @@ -946,7 +964,7 @@ } return ElementDescriptor( libraryUris: uris, - kind: ElementKindUtilities.fromName(elementKey)!, + kind: ElementKind.fromName(elementKey)!, isStatic: isStatic, components: components, ); @@ -1160,13 +1178,12 @@ bool? replaceTarget; var replaceTargetNode = node.valueAt(_replaceTarget); replaceTarget = - replaceTargetNode == null - ? false - : _translateBool( - replaceTargetNode, - ErrorContext(key: _replaceTarget, parentNode: node), - ) ?? - false; + replaceTargetNode != null && + (_translateBool( + replaceTargetNode, + ErrorContext(key: _replaceTarget, parentNode: node), + ) ?? + false); var argumentsNode = node.valueAt(_arguments); var argumentList = argumentsNode == null @@ -1474,6 +1491,14 @@ ElementKind.setterKind, ElementKind.variableKind, }); + // Type declarations can replace each other. + addSet({ + ElementKind.classKind, + ElementKind.enumKind, + ElementKind.extensionTypeKind, + ElementKind.mixinKind, + ElementKind.typedefKind, + }); return types; } }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart index 96d553f..259739a 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart
@@ -487,7 +487,7 @@ date: DateTime.now(), element: ElementDescriptor( libraryUris: [Uri.parse(importUri)], - kind: ElementKindUtilities.fromName(_kind)!, + kind: ElementKind.fromName(_kind)!, isStatic: isStatic, components: components ?? ['m', 'C'], ),
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart index a930feb..62048c7 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart
@@ -13,18 +13,26 @@ import 'package:analyzer/utilities/package_config_file_builder.dart'; import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import '../../../../../abstract_single_unit.dart'; import '../fix_processor.dart'; /// A base class defining support for writing fix processor tests for /// data-driven fixes. -abstract class DataDrivenFixProcessorTest extends FixProcessorTest { - /// Return the URI used to import the library created by [setPackageContent]. +abstract class DataDrivenBulkFixProcessorTest extends BulkFixProcessorTest + with DataDrivenFixProcessorTestMixin {} + +/// A base class defining support for writing fix processor tests for +/// data-driven fixes. +abstract class DataDrivenFixProcessorTest extends FixProcessorTest + with DataDrivenFixProcessorTestMixin {} + +mixin DataDrivenFixProcessorTestMixin on AbstractSingleUnitTest { + /// Returns the URI used to import the library created by [setPackageContent]. String get importUri => 'package:p/lib.dart'; - @override FixKind get kind => DartFixKind.DATA_DRIVEN; - /// Add the file containing the data used by the data-driven fix with the + /// Adds the file containing the data used by the data-driven fix with the /// given [content]. void addPackageDataFile(String content) { newFile( @@ -33,7 +41,7 @@ ); } - /// Add the file in the SDK containing the data used by the data-driven fix + /// Adds the file in the SDK containing the data used by the data-driven fix /// with the given [content]. void addSdkDataFile(String content) { newFile( @@ -42,7 +50,7 @@ ); } - /// Return a code template that will produce the given [text]. + /// Returns a code template that will produce the given [text]. CodeTemplate codeTemplate(String text) { return CodeTemplate(CodeTemplateKind.expression, [ TemplateText(text), @@ -54,7 +62,7 @@ bool ignoreUnusedImport(Diagnostic diagnostic) => diagnostic.diagnosticCode != WarningCode.UNUSED_IMPORT; - /// Set the content of the library that defines the element referenced by the + /// Sets the content of the library that defines the element referenced by the /// data on which this test is based. void setPackageContent(String content) { newFile('$workspaceRootPath/p/lib/lib.dart', content); @@ -65,7 +73,7 @@ ); } - /// Set the data on which this test is based. + /// Sets the data on which this test is based. void setPackageData(Transform transform) { DataDriven.transformSetsForTests = [ TransformSet()..addTransform(transform),
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/invalid_change_for_kind_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/invalid_change_for_kind_test.dart index 67e58b8..03565b3 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/invalid_change_for_kind_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/invalid_change_for_kind_test.dart
@@ -24,14 +24,14 @@ date: 2021-11-30 element: uris: ['test.dart'] - class: 'C' + extension: 'E' changes: - kind: 'replacedBy' newElement: uris: ['test.dart'] - class: 'D' + extension: 'F' ''', - [error(TransformSetErrorCode.invalidChangeForKind, 173, 39)], + [error(TransformSetErrorCode.invalidChangeForKind, 177, 43)], ); } }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart index e3a72ae..7174b6f 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart
@@ -60,10 +60,14 @@ static List<ElementKind> invocationKinds = [ ElementKind.classKind, ElementKind.constructorKind, + ElementKind.enumKind, ElementKind.extensionKind, + ElementKind.extensionTypeKind, ElementKind.functionKind, ElementKind.getterKind, ElementKind.methodKind, + ElementKind.mixinKind, + ElementKind.typedefKind, ]; /// The kinds that are expected where a method or constructor is allowed. @@ -76,6 +80,7 @@ static List<ElementKind> typeKinds = [ ElementKind.classKind, ElementKind.enumKind, + ElementKind.extensionTypeKind, ElementKind.mixinKind, ElementKind.typedefKind, ];
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart index 1c66e23..5dea2ef 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart
@@ -1177,7 +1177,7 @@ date: DateTime.now(), element: ElementDescriptor( libraryUris: [Uri.parse(importUri)], - kind: ElementKindUtilities.fromName(_kind)!, + kind: ElementKind.fromName(_kind)!, isStatic: isStatic, components: originalComponents, ),
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_parameter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_parameter_test.dart index 0637843..56d11f4 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_parameter_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_parameter_test.dart
@@ -507,7 +507,7 @@ date: DateTime.now(), element: ElementDescriptor( libraryUris: [Uri.parse(importUri)], - kind: ElementKindUtilities.fromName(_kind)!, + kind: ElementKind.fromName(_kind)!, isStatic: isStatic, components: components, ),
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart index 367cafe..6c10515 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart
@@ -16,6 +16,7 @@ defineReflectiveTests(RenameClassTest); defineReflectiveTests(RenameConstructorTest); defineReflectiveTests(RenameExtensionTest); + defineReflectiveTests(RenameExtensionTypeTest); defineReflectiveTests(RenameFieldTest); defineReflectiveTests(RenameGetterTest); defineReflectiveTests(RenameSetterTest); @@ -759,6 +760,115 @@ } @reflectiveTest +class RenameExtensionTypeTest extends _AbstractRenameTest { + @override + String get _kind => 'extensionType'; + + Future<void> test_override_deprecated() async { + setPackageContent(''' +@deprecated +extension type Old(String _) implements String { + int get double => length * 2; +} +extension type New(String _) implements String { + int get double => length * 2; +} +'''); + setPackageData(_rename(['Old'], 'New')); + await resolveTestCode(''' +import '$importUri'; + +var l = Old('a').double; +'''); + await assertHasFix(''' +import '$importUri'; + +var l = New('a').double; +'''); + } + + Future<void> test_override_removed() async { + setPackageContent(''' +extension type New(String _) implements String { + int get double => length * 2; +} +'''); + setPackageData(_rename(['Old'], 'New')); + await resolveTestCode(''' +import '$importUri'; + +var l = Old('a').double; +'''); + await assertHasFix(''' +import '$importUri'; + +var l = New('a').double; +'''); + } + + Future<void> test_staticField_deprecated() async { + setPackageContent(''' +@deprecated +extension type Old(String _) implements String { + static String empty = ''; +} +extension type New(String _) implements String { + static String empty = ''; +} +'''); + setPackageData(_rename(['Old'], 'New')); + await resolveTestCode(''' +import '$importUri'; + +var s = Old.empty; +'''); + await assertHasFix(''' +import '$importUri'; + +var s = New.empty; +'''); + } + + Future<void> test_staticField_removed() async { + setPackageContent(''' +extension type New(String _) implements String { + static String empty = ''; +} +'''); + setPackageData(_rename(['Old'], 'New')); + await resolveTestCode(''' +import '$importUri'; + +var s = Old.empty; +'''); + await assertHasFix(''' +import '$importUri'; + +var s = New.empty; +'''); + } + + Future<void> test_staticField_removed_prefixed() async { + setPackageContent(''' +extension type New(String _) implements String { + static String empty = ''; +} +'''); + setPackageData(_rename(['Old'], 'New')); + await resolveTestCode(''' +import '$importUri' as p; + +var s = p.Old.empty; +'''); + await assertHasFix(''' +import '$importUri' as p; + +var s = p.New.empty; +'''); + } +} + +@reflectiveTest class RenameFieldTest extends _AbstractRenameTest { @override String get _kind => 'field'; @@ -1695,7 +1805,7 @@ date: DateTime.now(), element: ElementDescriptor( libraryUris: [Uri.parse(importUri)], - kind: ElementKindUtilities.fromName(_kind)!, + kind: ElementKind.fromName(_kind)!, isStatic: isStatic, components: components, ),
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart index 51a0735..44378f7 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_test.dart
@@ -20,6 +20,41 @@ @reflectiveTest class ReplacedByTest extends DataDrivenFixProcessorTest { + Future<void> test_class_class() async { + await _assertReplacement( + _Element.class_(isDeprecated: true, isOld: true), + _Element.class_(), + ); + } + + Future<void> test_class_enum() async { + await _assertReplacement( + _Element.class_(isDeprecated: true, isOld: true), + _Element.enum_(), + ); + } + + Future<void> test_class_extensionType() async { + await _assertReplacement( + _Element.class_(isDeprecated: true, isOld: true), + _Element.extensionType(), + ); + } + + Future<void> test_class_mixin() async { + await _assertReplacement( + _Element.class_(isDeprecated: true, isOld: true), + _Element.mixin(), + ); + } + + Future<void> test_class_typedef() async { + await _assertReplacement( + _Element.class_(isDeprecated: true, isOld: true), + _Element.typedef(aliasedType: 'int'), + ); + } + Future<void> test_defaultConstructor_defaultConstructor() async { await _assertReplacement( _Element.defaultConstructor(isDeprecated: true, isOld: true), @@ -63,6 +98,41 @@ ); } + Future<void> test_enum_class() async { + await _assertReplacement( + _Element.enum_(isDeprecated: true, isOld: true), + _Element.class_(), + ); + } + + Future<void> test_enum_enum() async { + await _assertReplacement( + _Element.enum_(isDeprecated: true, isOld: true), + _Element.enum_(), + ); + } + + Future<void> test_enum_extensionType() async { + await _assertReplacement( + _Element.enum_(isDeprecated: true, isOld: true), + _Element.extensionType(), + ); + } + + Future<void> test_enum_mixin() async { + await _assertReplacement( + _Element.enum_(isDeprecated: true, isOld: true), + _Element.mixin(), + ); + } + + Future<void> test_enum_typedef() async { + await _assertReplacement( + _Element.enum_(isDeprecated: true, isOld: true), + _Element.typedef(aliasedType: 'int'), + ); + } + Future<void> test_enumConstant_enumConstant() async { await _assertReplacement( _Element.constant(isDeprecated: true, isOld: true), @@ -138,6 +208,41 @@ ); } + Future<void> test_extensionType_class() async { + await _assertReplacement( + _Element.extensionType(isDeprecated: true, isOld: true), + _Element.class_(), + ); + } + + Future<void> test_extensionType_enum() async { + await _assertReplacement( + _Element.extensionType(isDeprecated: true, isOld: true), + _Element.enum_(), + ); + } + + Future<void> test_extensionType_extensionType() async { + await _assertReplacement( + _Element.extensionType(isDeprecated: true, isOld: true), + _Element.extensionType(), + ); + } + + Future<void> test_extensionType_mixin() async { + await _assertReplacement( + _Element.extensionType(isDeprecated: true, isOld: true), + _Element.mixin(), + ); + } + + Future<void> test_extensionType_typedef() async { + await _assertReplacement( + _Element.extensionType(isDeprecated: true, isOld: true), + _Element.typedef(aliasedType: 'int'), + ); + } + Future<void> test_function_function_invocation() async { await _assertReplacement( _Element.topLevelFunction(isDeprecated: true, isOld: true), @@ -309,6 +414,41 @@ await assertNoFix(); } + Future<void> test_mixin_class() async { + await _assertReplacement( + _Element.mixin(isDeprecated: true, isOld: true), + _Element.class_(), + ); + } + + Future<void> test_mixin_enum() async { + await _assertReplacement( + _Element.mixin(isDeprecated: true, isOld: true), + _Element.enum_(), + ); + } + + Future<void> test_mixin_extensionType() async { + await _assertReplacement( + _Element.mixin(isDeprecated: true, isOld: true), + _Element.extensionType(), + ); + } + + Future<void> test_mixin_mixin() async { + await _assertReplacement( + _Element.mixin(isDeprecated: true, isOld: true), + _Element.mixin(), + ); + } + + Future<void> test_mixin_typedef() async { + await _assertReplacement( + _Element.mixin(isDeprecated: true, isOld: true), + _Element.typedef(aliasedType: 'int'), + ); + } + Future<void> test_namedConstructor_defaultConstructor() async { await _assertReplacement( _Element.namedConstructor(isDeprecated: true, isOld: true), @@ -911,6 +1051,41 @@ ); } + Future<void> test_typedef_class() async { + await _assertReplacement( + _Element.typedef(isDeprecated: true, isOld: true, aliasedType: 'int'), + _Element.class_(), + ); + } + + Future<void> test_typedef_enum() async { + await _assertReplacement( + _Element.typedef(isDeprecated: true, isOld: true, aliasedType: 'int'), + _Element.enum_(), + ); + } + + Future<void> test_typedef_extensionType() async { + await _assertReplacement( + _Element.typedef(isDeprecated: true, isOld: true, aliasedType: 'int'), + _Element.extensionType(), + ); + } + + Future<void> test_typedef_mixin() async { + await _assertReplacement( + _Element.typedef(isDeprecated: true, isOld: true, aliasedType: 'int'), + _Element.mixin(), + ); + } + + Future<void> test_typedef_typedef() async { + await _assertReplacement( + _Element.typedef(isDeprecated: true, isOld: true, aliasedType: 'int'), + _Element.typedef(aliasedType: 'int'), + ); + } + Future<void> _assertReplacement( _Element oldElement, _Element newElement, { @@ -1083,6 +1258,48 @@ '''); } + Future<void> test_new_element_uris_prefixed() async { + setPackageContent(''' +@deprecated +void expect(Object? v1, Object? v2) {} +'''); + newFile('$workspaceRootPath/p/lib/lib2.dart', ''' +void expect(Object? v1, Object? v2) {} +'''); + addPackageDataFile(''' +version: 1 +transforms: + - title: 'Replace expect' + date: 2022-05-12 + bulkApply: false + element: + uris: ['$importUri'] + function: 'expect' + changes: + - kind: 'replacedBy' + newElement: + uris: ['package:p/lib2.dart'] + function: 'expect' +'''); + + /// Uses the unprefixed name from the new unprefixed import. + await resolveTestCode(''' +import '$importUri' as prefix; + +main() { + prefix.expect(true, true); +} +'''); + await assertHasFix(''' +import '$importUri' as prefix; +import 'package:p/lib2.dart'; + +main() { + expect(true, true); +} +'''); + } + Future<void> test_new_element_uris_single() async { setPackageContent(''); addPackageDataFile(''' @@ -1116,6 +1333,48 @@ } '''); } + + Future<void> test_new_element_uris_to_prefixed() async { + setPackageContent(''' +@deprecated +void expect(Object? v1, Object? v2) {} +'''); + newFile('$workspaceRootPath/p/lib/lib2.dart', ''' +void expect(Object? v1, Object? v2) {} +'''); + addPackageDataFile(''' +version: 1 +transforms: + - title: 'Replace expect' + date: 2022-05-12 + bulkApply: false + element: + uris: ['$importUri'] + function: 'expect' + changes: + - kind: 'replacedBy' + newElement: + uris: ['package:p/lib2.dart'] + function: 'expect' +'''); + // Use the prefix of the existing prefixed import. + await resolveTestCode(''' +import '$importUri'; +import 'package:p/lib2.dart' as prefix; // ignore: unused_import + +main() { + expect(true, true); +} +'''); + await assertHasFix(''' +import '$importUri'; +import 'package:p/lib2.dart' as prefix; // ignore: unused_import + +main() { + prefix.expect(true, true); +} +'''); + } } class _Element { @@ -1125,7 +1384,6 @@ _Element(this.kind, this.components, this.declaration); - // ignore: unused_element factory _Element.class_({bool isDeprecated = false, bool isOld = false}) { var name = isOld ? 'C_old' : 'C_new'; var annotation = _annotation(isDeprecated: isDeprecated, isTopLevel: true); @@ -1167,7 +1425,6 @@ ); } - // ignore: unused_element factory _Element.enum_({bool isDeprecated = false, bool isOld = false}) { var enumName = isOld ? 'E_old' : 'E_new'; var constantName = isOld ? 'c_old' : 'c_new'; @@ -1180,6 +1437,28 @@ ); } + factory _Element.extensionType({ + bool isDeprecated = false, + bool isOld = false, + String representationType = 'int', + String representationVariable = '_', + String? constructorName, + bool isConst = false, + }) { + var name = isOld ? 'X_old' : 'X_new'; + var annotation = _annotation(isDeprecated: isDeprecated, isTopLevel: true); + var constructorPart = + '${constructorName == null ? '' : '.$constructorName'}' + '($representationType $representationVariable)'; + var constPrefix = isConst ? 'const ' : ''; + return _Element( + ElementKind.extensionTypeKind, + [name], + ''' +${annotation}extension type $constPrefix$name$constructorPart {}''', + ); + } + factory _Element.field({ bool isDeprecated = false, bool isOld = false, @@ -1237,6 +1516,17 @@ ); } + factory _Element.mixin({bool isDeprecated = false, bool isOld = false}) { + var name = isOld ? 'M_old' : 'M_new'; + var annotation = _annotation(isDeprecated: isDeprecated, isTopLevel: true); + return _Element( + ElementKind.mixinKind, + [name], + ''' +${annotation}mixin $name {}''', + ); + } + factory _Element.namedConstructor({ bool isDeprecated = false, bool isOld = false, @@ -1329,15 +1619,18 @@ ); } - // ignore: unused_element - factory _Element.typedef({bool isDeprecated = false, bool isOld = false}) { + factory _Element.typedef({ + bool isDeprecated = false, + bool isOld = false, + String aliasedType = 'int Function()', + }) { var name = isOld ? 'T_old' : 'T_new'; var annotation = _annotation(isDeprecated: isDeprecated); return _Element( ElementKind.typedefKind, [name], ''' -${annotation}typedef $name = int Function();''', +${annotation}typedef $name = $aliasedType;''', ); }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_type_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_type_test.dart new file mode 100644 index 0000000..9d9e206 --- /dev/null +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/replaced_by_type_test.dart
@@ -0,0 +1,543 @@ +// 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. + +@Timeout.none +library; + +import 'dart:io'; + +import 'package:test/test.dart' show Timeout; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'data_driven_test_support.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(ReplacedByTypeTest); + }); +} + +const String fixFileHeader = ''' +version: 1 +transforms: +'''; + +@reflectiveTest +class ReplacedByTypeTest extends DataDrivenBulkFixProcessorTest { + /// Data shared by all tests, computed once. + static ( + String lib1, + String lib2, + String fixData, + String fixTemplate, + Map<String, String> newNameOf, + )? + sharedData; + + static final _templateEntryRE = RegExp(r'%(\w+)%'); + + Future<void> test_no_prefix_no_import() async { + await _assertTemplatedFixes('', null); + } + + Future<void> test_no_prefix_no_prefix() async { + await _assertTemplatedFixes('', ''); + } + + Future<void> test_no_prefix_prefix() async { + await _assertTemplatedFixes('', 'q'); + } + + Future<void> test_prefix_no_import() async { + await _assertTemplatedFixes('p', null); + } + + Future<void> test_prefix_no_prefix() async { + await _assertTemplatedFixes('p', ''); + } + + Future<void> test_prefix_other_prefix() async { + await _assertTemplatedFixes('p', 'q'); + } + + Future<void> test_prefix_same_prefix() async { + await _assertTemplatedFixes('p', 'p'); + } + + void writeFix( + StringBuffer buffer, + TypeKind oldKind, + TypeKind newKind, + String oldName, + String newName, + bool otherLibrary, + ) { + if (buffer.isEmpty) buffer.write(fixFileHeader); + var lib1Uri = importUri; + const lib2Uri = 'package:p/lib2.dart'; + buffer.write(''' + - title: 'Replace with different type - ${oldKind.name}-${newKind.name} - $newName' + date: 2022-09-28 + element: + uris: ['$lib1Uri'] + ${oldKind.name}: '$oldName' + changes: + - kind: 'replacedBy' + newElement: + uris: ['${otherLibrary ? lib2Uri : lib1Uri}'] + ${newKind.name}: '$newName' +'''); + } + + void writeTemplatesFor( + StringBuffer buffer, + TypeKind oldKind, + TypeKind newKind, + String oldName, + bool generic, + bool removed, + ) { + /// There is a number of cases where a removed type is not recognized + /// by the fix. Only correct for unprefixed names. + const typeParams = '<T>'; + const typeArguments = '<int>'; + for (var nullable in [false, true]) { + var isNull = nullable('Nullable'); + var q = nullable('?'); + var type = '%$oldName%${generic(typeArguments)}$q'; + buffer.write(''' +// Global variable type${nullable(', nullable')}. +$type value$oldName$isNull = 0 as dynamic; + +// Parameter type${nullable(', nullable')}. +void func$oldName$isNull($type input) { + // Local variable type${nullable(', nullable')}. + $type value = input as dynamic; + print(value); +} + +/// Type argument${nullable(', nullable')}. +List<$type> list$oldName$isNull = []; + +/// Type bound${nullable(', nullable')}. +class GenericClass$oldName$isNull<T extends $type> {} + +/// Cast${nullable(', nullable')}. +Object? exprCast$oldName$isNull = 0 as $type; + +${removed ? '' : ''' +/// Type check${nullable(', nullable')}. +bool exprCheck$oldName$isNull = 0 is $type; +'''} +bool casePatternCast$oldName$isNull(Object o) { + switch (o) { + // Case cast${nullable(', nullable')}. + case _ as $type: return false; + } +} +bool casePatternVar$oldName$isNull(Object? o) { + switch (o) { + // Case variable pattern${nullable(', nullable')}. + case $type _: return false; + case _: return true; + } +} + +void declPatterCast$oldName$isNull(Object? o) { + // Declaration pattern cast${nullable(', nullable')}. + var (value as $type,) = o as dynamic; + print(value); +} + +void declPatternVar$oldName$isNull(Object? o) { + // Declaration pattern type annotation${nullable(', nullable')}. + var ($type value,) = 0 as dynamic; + print(value); +} +${(oldKind.canBeExtensionOnType && newKind.canBeExtensionOnType)(''' +// Extension on-type${nullable(', nullable')}. +extension Ext$oldName$isNull${generic(typeParams)} on $type {} +''')} +// Extension type representation type${nullable(', nullable')}. +extension type ExtType$oldName$isNull($type _) {} + +'''); + } + // Uses where the reference cannot be explicitly nullable (not used as type, + // or must be non-nullable.) + + var type = '%$oldName%${generic(typeArguments)}'; + buffer.write(''' +${removed ? '' : ''' +void tryOn$oldName(Object o) { + try { + throw o; + } on $type {} +} +'''} +// As type literal. +Type typeLiteral$oldName = $type; +// As const type literal. +const Type constTypeLiteral$oldName = $type; + +bool caseConst$oldName(Object o) { + switch (o) { + case const ($type): return false; + case _: return true; + } +} + +bool caseObjectPattern$oldName(Object o) { + // Object pattern check. + switch (o) { + case $type(): return false; + case _: return true; + } +} + +void declObjectPattern$oldName(Object? o) { + var ($type(runtimeType: value),) = 0 as dynamic; + print(value); +} +${(oldKind.canBeClassSuper && newKind.canBeClassSuper)(''' +class ClassExtends$oldName extends $type {} +''')} +${(oldKind.canBeClassMixin && newKind.canBeClassMixin)(''' +class ClassWith$oldName with $type {} +enum EnumWith$oldName with $type { e } +''')} +${(oldKind.canBeClassInterface && newKind.canBeClassInterface)(''' +abstract class ClassImplements$oldName implements $type {} +enum EnumImplements$oldName implements $type { e } +mixin MixinImplements$oldName implements $type {} +''')} +${(oldKind.canBeExtensionTypeInterface && newKind.canBeExtensionTypeInterface && !removed)(''' +extension type ExtensionTypeImplements$oldName($type _) implements $type {} +''')} +${(oldKind.canBeMixinOnType && newKind.canBeMixinOnType)(''' +mixin MixinOn$oldName on $type {} +''')} +'''); + // Static/constructor Member access + buffer.writeln('var staticAccess$oldName = <Object?>['); + // Constructors, for anything but mixins. + if (oldKind.hasConstructors && newKind.hasConstructors) { + for (var name in ['', '.new', '.named']) { + for (var op in ['', 'new ', 'const ']) { + if (!removed || op == '' && name == '') { + buffer.writeln(' $op$type$name(),'); + } + } + if (name.isNotEmpty) { + buffer.writeln(' $type$name, // Tearoff'); + } + } + } + + // Static member access, no generics. + // Same code for generic and non-generic types, so only do it for one. + if (!generic) { + type = '%$oldName%'; + buffer.write(''' + $type.constValue, // static constant + $type.value, // Static getter + $type.method(), // Static method + $type.method, // Static method tear-off, + ]; + + void staticSetter$oldName(dynamic value) { + $type.value = value; + } + '''); + } else { + buffer.write(' ];\n'); + } + } + + void writeTypeDeclaration( + StringBuffer buffer, + TypeKind kind, + String name, { + bool deprecated = false, + bool generic = false, + }) { + const typeParameters = '<T>'; + const typeArguments = '<int>'; + if (deprecated) buffer.writeln('@deprecated'); + switch (kind) { + // Type aliases have a helper class that they alias. + case TypeKind.typedefKind: + buffer.write(''' +typedef $name${generic(typeParameters)} = _T$name${generic(typeParameters)}; +'''); + writeTypeDeclaration( + buffer, + TypeKind.classKind, + '_T$name', + deprecated: deprecated, + generic: generic, + ); + case TypeKind.classKind: + buffer.write(''' +mixin class $name${generic(typeParameters)} { + static const Object? constValue = $name${generic('<Never>')}(); + static int value = 0; + static int method() => 0; + const $name(); + const $name.named(); +} +'''); + // Enums use a helper extension type to implement factory constructors. + case TypeKind.enumKind: + buffer.write(''' +enum $name${generic(typeParameters)} { + constValue${generic(typeArguments)}._(); + const $name._(); + static int value = 0; + static int method() => 0; + const factory $name() = _${name}Helper${generic(typeParameters)}; + const factory $name.named() = _${name}Helper${generic(typeParameters)}; +}${deprecated('\n@deprecated')} +extension type const _${name}Helper${generic(typeParameters)}._($name${generic(typeParameters)} _) + implements $name${generic(typeParameters)} { + const _${name}Helper() : this._($name.constValue as dynamic); +} +'''); + case TypeKind.mixinKind: + buffer.write(''' +mixin $name${generic(typeParameters)} { + static const Object? constValue = 0; + static int value = 0; + static int method() => 0; +} +'''); + case TypeKind.extensionTypeKind: + buffer.write(''' +extension type const $name${generic(typeParameters)}._(int _) implements Object { + static const Object? constValue = 0; + static int value = 0; + static int method() => 0; + const $name() : this._(0); + const $name.named() : this._(0); +} +'''); + } + } + + Future<void> _assertExpectedFixes(String source, String expected) async { + var (lib1, lib2, fixData, _, _) = (sharedData ??= _generateFiles()); + setPackageContent(lib1); + newFile('$workspaceRootPath/p/lib/lib2.dart', lib2); + addPackageDataFile(fixData); + await resolveTestCode(source); + await assertHasFix(expected); + } + + /// Checks that code in the template gets fixed. + /// + /// If [lib1Prefix] is non-empty, `lib1` is imported with that prefix. + /// If it's empty, `lib1` is imported with no prefix. + /// + /// If [lib2Prefix] is `null`, `lib2` is not pre-imported. + /// If non-`null`, it's imported with no prefix if empty, + /// or that prefix if not. + Future<void> _assertTemplatedFixes( + String lib1Prefix, + String? lib2Prefix, + ) async { + var (_, _, _, template, newNameOf) = (sharedData ??= _generateFiles()); + var lib1Import = + '''import '$importUri'${lib1Prefix.isNotEmpty(' as $lib1Prefix')};\n'''; + var lib1PrefixDot = lib1Prefix.isEmpty ? '' : '$lib1Prefix.'; + // Lib2 import in post-fix library. + var lib2Import = + '''import 'package:p/lib2.dart'${(lib2Prefix != null && lib2Prefix.isNotEmpty)(' as $lib2Prefix')};\n'''; + var lib2PrefixDot = ''; + var lib2Usage = ''; + var lib2OldImport = ''; // Import in pre-fix library. + if (lib2Prefix != null) { + // Lib2 was imported in original. + if (lib2Prefix.isNotEmpty) lib2PrefixDot = '$lib2Prefix.'; + lib2OldImport = lib2Import; + // Inserted to avoid a pre-imported lib 2 being unused. + lib2Usage = '${lib2PrefixDot}Lib2Use? dummyDeclaration;\n'; + } + + var source = + '$lib1Import$lib2OldImport$lib2Usage${_replaceInTemplate(template, lib1PrefixDot, lib2PrefixDot, null)}'; + var expected = + '$lib1Import$lib2Import$lib2Usage${_replaceInTemplate(template, lib1PrefixDot, lib2PrefixDot, newNameOf)}'; + + File( + 'gen_old_$lib1Prefix${lib2Prefix ?? 'n'}.dart', + ).writeAsStringSync(source.toString().replaceAll('package:p/', 'gen_')); + File( + 'gen_new_$lib1Prefix${lib2Prefix ?? 'n'}.dart', + ).writeAsStringSync(expected.toString().replaceAll('package:p/', 'gen_')); + + await _assertExpectedFixes(source, expected); + } + + /// Creates the types referenced by the files to be fixed, + /// and a template for all the occurrences of those types. + /// + /// * `lib1`: The primary library containing all the deprecated classes, + /// and some of the classes they should be replaced by. + /// * `lib2`: Secondary library for replacing a deprecated type with + /// a type in another library. + /// * `fixTemplate` is a template containing `%oldName%` where a type + /// to replace should go. Inserting a deprecated type and fixing + /// should give the corresponding name from `newNameOf`. + /// * `fixData` the data-driven fixes mapping all deprecated types in `lib1` + /// to new types in `lib1` or `lib2`. + /// * Mapping from deprecated type names to their replacement type name. + ( + String lib1, + String lib2, + String fixData, + String fixTemplate, + Map<String, String> newNameOf, + ) + _generateFiles() { + var lib1Buffer = StringBuffer(); + var lib2Buffer = StringBuffer(''' +class Lib2Use {} // Can be referred to avoid unused import warnings. +'''); + var fixDataBuffer = StringBuffer(); + + // Contains code templates for code to fix. + // A character sequence of `%ABCDE%` represents an occurrence of the old + // type `ABCDE`. It will be replaced in both before source (to add prefixes + // in some cases), and in output to insert the expected replacement. + var templateBuffer = StringBuffer(); + + /// Mapping from old deprecated name to new replacement name. + var newNameOf = <String, String>{}; + + // Go through all the combinations of kinds of type declarations, and + // whether the replacement is in the same or another library. + for (var newKind in TypeKind.values) { + for (var otherLibrary in [false, true]) { + for (var generic in [false, true]) { + var newName = '${generic('G')}${otherLibrary('O')}$newKind'; + writeTypeDeclaration( + otherLibrary ? lib2Buffer : lib1Buffer, + newKind, + newName, + generic: generic, + ); + for (var oldKind in TypeKind.values) { + for (var removed in [false /*true*/]) { + // Should also work when original type declaration is removed + // Does not yet, not in all cases. + var oldName = '${removed ? 'R' : 'D'}$oldKind$newName'; + assert(!newNameOf.containsKey(oldName)); + if (!removed) { + writeTypeDeclaration( + lib1Buffer, + oldKind, + oldName, + deprecated: true, + generic: generic, + ); + } + writeFix( + fixDataBuffer, + oldKind, + newKind, + oldName, + newName, + otherLibrary, + ); + newNameOf[oldName] = newName; + + writeTemplatesFor( + templateBuffer, + oldKind, + newKind, + oldName, + generic, + removed, + ); + } + } + } + } + } + File('gen_lib.dart').writeAsStringSync(lib1Buffer.toString()); + File('gen_lib2.dart').writeAsStringSync(lib2Buffer.toString()); + File('gen_fix_data.yaml').writeAsStringSync(fixDataBuffer.toString()); + return ( + lib1Buffer.toString(), + lib2Buffer.toString(), + fixDataBuffer.toString(), + templateBuffer.toString(), + newNameOf, + ); + } + + /// Replaces `%oldName%` with either` `(prefix.)?oldName` + /// or `(prefix.)?newName`. + static String _replaceInTemplate( + String template, + String lib1PrefixDot, + String lib2PrefixDot, + Map<String, String>? newNameOf, + ) { + if (newNameOf == null) { + // Old name, just prefix with prefix-dot if needed. + return template.replaceAllMapped( + _templateEntryRE, + (m) => '$lib1PrefixDot${m[1]}', + ); + } + var cache = <String, String>{}; + return template.replaceAllMapped(_templateEntryRE, (m) { + var oldName = m[1]!; + + String? newName = cache[oldName]; + if (newName != null) return newName; + newName = newNameOf[oldName]!; + var prefix = newName.contains('O') ? lib2PrefixDot : lib1PrefixDot; + return cache[oldName] = '$prefix$newName'; + }); + } +} + +enum TypeKind { + classKind('class', 'C'), + enumKind('enum', 'E'), + extensionTypeKind('extensionType', 'X'), + mixinKind('mixin', 'M'), + typedefKind('typedef', 'A'); + + // String used to represent kind in fix-data and pretty names. + final String name; + // Single character used to represent the type in constructed names. + final String char; + + const TypeKind(this.name, this.char); + + bool get canBeClassInterface => + this == classKind || this == mixinKind || this == typedefKind; + // Because class declarations here are all mixin classes. + bool get canBeClassMixin => + this == classKind || this == mixinKind || this == typedefKind; + bool get canBeClassSuper => this == classKind || this == typedefKind; + bool get canBeExtensionOnType => true; + bool get canBeExtensionTypeInterface => + canBeClassInterface || this == extensionTypeKind; + bool get canBeMixinOnType => + this == classKind || this == mixinKind || this == typedefKind; + // Mixin declarations can't declare constructors, not even factory ones. Yet. + bool get hasConstructors => this != mixinKind; + + @override + String toString() => char; +} + +extension on bool { + /// Choose a string based on the boolean value. + String call(String ifTrue, [String ifFalse = '']) => this ? ifTrue : ifFalse; +}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart index 4ee3821..fe6201b 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
@@ -18,6 +18,7 @@ import 'rename_parameter_test.dart' as rename_parameter; import 'rename_test.dart' as rename; import 'replaced_by_test.dart' as replaced_by; +import 'replaced_by_type_test.dart' as replaced_by_type; import 'sdk_fix_test.dart' as sdk_fix; import 'test_use_case_test.dart' as test_use_case; import 'transform_set_manager_test.dart' as transform_set_manager; @@ -39,6 +40,7 @@ rename_parameter.main(); rename.main(); replaced_by.main(); + replaced_by_type.main(); sdk_fix.main(); test_use_case.main(); transform_set_manager.main();
diff --git a/pkg/analysis_server_plugin/lib/src/correction/fix_processor.dart b/pkg/analysis_server_plugin/lib/src/correction/fix_processor.dart index 762c4de..9fe82b6 100644 --- a/pkg/analysis_server_plugin/lib/src/correction/fix_processor.dart +++ b/pkg/analysis_server_plugin/lib/src/correction/fix_processor.dart
@@ -47,7 +47,7 @@ /// If passing [alreadyCalculated] for calculations where we know the output /// will be the same, a result will only be calculated if one hasn't been /// calculated already (for a similar situation). If calculating the Set will - /// be ammended with this information. + /// be amended with this information. /// If not passing [alreadyCalculated] the calculation will always be /// performed. FixProcessor(
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index 6077a3a..fafb150 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -16464,7 +16464,7 @@ /// there's no target. /// /// In an ordinary method invocation this is either a period (`.`) or a - /// null-aware opertator (`?.`). In a cascade section this is the cascade + /// null-aware operator (`?.`). In a cascade section this is the cascade /// operator ('..'). Token? get operator;
diff --git a/pubspec.yaml b/pubspec.yaml index 120d380..6f994c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -212,6 +212,8 @@ path: third_party/pkg/leak_tracker/pkgs/memory_usage mime: path: third_party/pkg/tools/pkgs/mime + native_test_helpers: + path: third_party/pkg/native/pkgs/native_test_helpers native_toolchain_c: path: third_party/pkg/native/pkgs/native_toolchain_c oauth2: @@ -272,8 +274,6 @@ path: third_party/pkg/tools/pkgs/string_scanner swift2objc: path: third_party/pkg/native/pkgs/swift2objc - swiftgen: - path: third_party/pkg/native/pkgs/swiftgen sync_http: path: third_party/pkg/sync_http tar:
diff --git a/third_party/pkg/native_toolchain_c.status b/third_party/pkg/native_toolchain_c.status index ac2c6a2..6cf3328 100644 --- a/third_party/pkg/native_toolchain_c.status +++ b/third_party/pkg/native_toolchain_c.status
@@ -10,6 +10,7 @@ test/clinker/treeshake_cross_android_test: SkipByDesign # Cross compilation is not tested on the Dart CI. test/clinker/treeshake_cross_ios_test: SkipByDesign # Cross compilation is not tested on the Dart CI. test/clinker/treeshake_cross_test: SkipByDesign # Cross compilation is not tested on the Dart CI. +test/clinker/windows_module_definition_cross_test: SkipByDesign # Cross compilation is not tested on the Dart CI. test/native_toolchain/gcc_test: SkipByDesign # Cross compilation is not tested on the Dart CI. test/native_toolchain/ndk_test: SkipByDesign # Cross compilation is not tested on the Dart CI.
diff --git a/tools/VERSION b/tools/VERSION index df6c089..6caf1039 100644 --- a/tools/VERSION +++ b/tools/VERSION
@@ -27,5 +27,5 @@ MAJOR 3 MINOR 9 PATCH 0 -PRERELEASE 327 +PRERELEASE 328 PRERELEASE_PATCH 0