| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:collection'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/dart/element/type.dart'; | |
| import 'package:analyzer/src/dart/ast/ast.dart'; | |
| import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; | |
| import 'package:analyzer/src/error/listener.dart'; | |
| import 'package:meta/meta.dart'; | |
| /// Checks whether a declaration violates the rules of [immutable]. | |
| class ImmutableVerifier extends SimpleAstVisitor2<void> { | |
| final DiagnosticReporter _diagnosticReporter; | |
| ImmutableVerifier(this._diagnosticReporter); | |
| /// Checks whether [node] violates the rules of [immutable]. | |
| /// | |
| /// If [node] is marked with [immutable] or inherits from a class or mixin | |
| /// marked with [immutable], this function searches the fields of [node] and | |
| /// its superclasses, reporting a warning if any non-final instance fields are | |
| /// found. | |
| void checkDeclaration( | |
| CompilationUnitMember node, { | |
| required Token nameToken, | |
| }) { | |
| var element = node.declaredFragment!.element as InterfaceElement; | |
| if (!_isOrInheritsImmutable(element, HashSet<InterfaceElement>())) { | |
| return; | |
| } | |
| Iterable<String> nonFinalFieldNames = | |
| _declaredAndInheritedNonFinalInstanceFields( | |
| element, | |
| HashSet<InterfaceElement>(), | |
| ); | |
| if (nonFinalFieldNames.isNotEmpty) { | |
| _diagnosticReporter.report( | |
| diag.mustBeImmutable | |
| .withArguments(fieldNames: nonFinalFieldNames.join(', ')) | |
| .at(nameToken), | |
| ); | |
| } | |
| } | |
| /// Returns all of the declared and ihherited non-final instance fields of | |
| /// [element]. | |
| /// | |
| /// [visited] is used to avoid visiting supertypes multiple times. | |
| static Iterable<String> _declaredAndInheritedNonFinalInstanceFields( | |
| InterfaceElement element, | |
| Set<InterfaceElement> visited, | |
| ) { | |
| if (!visited.add(element)) { | |
| // Already checked `element`. | |
| return const []; | |
| } | |
| return [ | |
| ...element.nonFinalInstanceFieldNames, | |
| ...element.mixins.expand( | |
| (mixin) => mixin.element.nonFinalInstanceFieldNames, | |
| ), | |
| if (element.supertype case var supertype?) | |
| ..._declaredAndInheritedNonFinalInstanceFields( | |
| supertype.element, | |
| visited, | |
| ), | |
| ]; | |
| } | |
| /// Returns whether the given class [element] or any superclass of it is | |
| /// annotated with the `@immutable` annotation. | |
| static bool _isOrInheritsImmutable( | |
| InterfaceElement element, | |
| Set<InterfaceElement> visited, | |
| ) { | |
| if (visited.add(element)) { | |
| if (element.metadata.hasImmutable) { | |
| return true; | |
| } | |
| for (InterfaceType interface in element.mixins) { | |
| if (_isOrInheritsImmutable(interface.element, visited)) { | |
| return true; | |
| } | |
| } | |
| for (InterfaceType mixin in element.interfaces) { | |
| if (_isOrInheritsImmutable(mixin.element, visited)) { | |
| return true; | |
| } | |
| } | |
| if (element.supertype != null) { | |
| return _isOrInheritsImmutable(element.supertype!.element, visited); | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| extension on InterfaceElement { | |
| List<String> get nonFinalInstanceFieldNames { | |
| var nonFinalFields = fields | |
| .where((f) => !f.isStatic && !f.isFinal && !f.isOriginGetterSetter) | |
| .toList(); | |
| if (nonFinalFields.isEmpty) { | |
| return const []; | |
| } | |
| return List.generate(nonFinalFields.length, (i) { | |
| var field = nonFinalFields[i]; | |
| return '$name.${field.name}'; | |
| }); | |
| } | |
| } |