| // Copyright (c) 2019, 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:convert'; |
| |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer_plugin/protocol/protocol_common.dart'; |
| import 'package:nnbd_migration/instrumentation.dart'; |
| import 'package:nnbd_migration/src/nullability_migration_impl.dart'; |
| import 'package:pub_semver/pub_semver.dart'; |
| |
| export 'package:nnbd_migration/src/utilities/hint_utils.dart' show HintComment; |
| |
| /// Description of fixes that might be performed by nullability migration. |
| class NullabilityFixDescription { |
| /// A `.then((value) => ...)` suffix was added to an expression. |
| static const addThen = NullabilityFixDescription._( |
| appliedMessage: 'Added `.then` to adjust type of Future expression', |
| kind: NullabilityFixKind.addThen); |
| |
| /// An import was added to the library. |
| static const addImport = NullabilityFixDescription._( |
| appliedMessage: 'Added import for use in migrated code', |
| kind: NullabilityFixKind.addImport); |
| |
| /// A variable declaration needs to be marked as "late". |
| static const addLate = NullabilityFixDescription._( |
| appliedMessage: 'Added a late keyword', kind: NullabilityFixKind.addLate); |
| |
| /// A variable declaration needs to be marked as "late" due to the presence of |
| /// a `/*late*/` hint. |
| static const addLateDueToHint = NullabilityFixDescription._( |
| appliedMessage: 'Added a late keyword, due to a hint', |
| kind: NullabilityFixKind.addLateDueToHint); |
| |
| /// A variable declaration needs to be marked as "late" due to being certainly |
| /// assigned in test setup. |
| static const addLateDueToTestSetup = NullabilityFixDescription._( |
| appliedMessage: 'Added a late keyword, due to assignment in `setUp`', |
| kind: NullabilityFixKind.addLateDueToTestSetup); |
| |
| /// A variable declaration needs to be marked as "late" and "final" due to the |
| /// presence of a `/*late final*/` hint. |
| static const addLateFinalDueToHint = NullabilityFixDescription._( |
| appliedMessage: 'Added late and final keywords, due to a hint', |
| kind: NullabilityFixKind.addLateFinalDueToHint); |
| |
| /// An expression's value needs to be null-checked. |
| static const checkExpression = NullabilityFixDescription._( |
| appliedMessage: 'Added a non-null assertion to nullable expression', |
| kind: NullabilityFixKind.checkExpression, |
| ); |
| |
| /// An expression's value will be null-checked due to a hint. |
| static const checkExpressionDueToHint = NullabilityFixDescription._( |
| appliedMessage: 'Accepted a null check hint', |
| kind: NullabilityFixKind.checkExpressionDueToHint, |
| ); |
| |
| /// A compound assignment's combiner operator returns a type that isn't |
| /// assignable to the LHS of the assignment. |
| static const compoundAssignmentHasBadCombinedType = |
| NullabilityFixDescription._( |
| appliedMessage: 'Compound assignment has bad combined type', |
| kind: NullabilityFixKind.compoundAssignmentHasBadCombinedType, |
| ); |
| |
| /// A compound assignment's LHS has a nullable type. |
| static const compoundAssignmentHasNullableSource = |
| NullabilityFixDescription._( |
| appliedMessage: 'Compound assignment has nullable source', |
| kind: NullabilityFixKind.compoundAssignmentHasNullableSource, |
| ); |
| |
| /// Informative message: a condition of an if-test or conditional expression |
| /// will always evaluate to `false` in strong checking mode. |
| static const conditionFalseInStrongMode = NullabilityFixDescription._( |
| appliedMessage: 'Condition will always be false in strong checking mode', |
| kind: NullabilityFixKind.conditionFalseInStrongMode); |
| |
| /// Informative message: a condition of an if-test or conditional expression |
| /// will always evaluate to `true` in strong checking mode. |
| static const conditionTrueInStrongMode = NullabilityFixDescription._( |
| appliedMessage: 'Condition will always be true in strong checking mode', |
| kind: NullabilityFixKind.conditionTrueInStrongMode); |
| |
| /// An if-test or conditional expression needs to have its condition |
| /// discarded. |
| static const discardCondition = NullabilityFixDescription._( |
| appliedMessage: 'Discarded a condition which is always true', |
| kind: NullabilityFixKind.removeDeadCode, |
| ); |
| |
| /// An if-test or conditional expression needs to have its condition and |
| /// "else" branch discarded. |
| static const discardElse = NullabilityFixDescription._( |
| appliedMessage: 'Discarded an unreachable conditional else branch', |
| kind: NullabilityFixKind.removeDeadCode, |
| ); |
| |
| /// An if-test or conditional expression needs to have its condition and |
| /// "then" branch discarded. |
| static const discardThen = NullabilityFixDescription._( |
| appliedMessage: |
| 'Discarded a condition which is always false, and the "then" branch ' |
| 'that follows', |
| kind: NullabilityFixKind.removeDeadCode, |
| ); |
| |
| /// An if-test needs to be discarded completely. |
| static const discardIf = NullabilityFixDescription._( |
| appliedMessage: 'Discarded an if-test with no effect', |
| kind: NullabilityFixKind.removeDeadCode, |
| ); |
| |
| static const downcastExpression = NullabilityFixDescription._( |
| appliedMessage: 'Added a downcast to an expression', |
| kind: NullabilityFixKind.downcastExpression, |
| ); |
| |
| /// Informative message: there is no valid migration for `null` in a |
| /// non-nullable context. |
| static const noValidMigrationForNull = NullabilityFixDescription._( |
| appliedMessage: 'No valid migration for expression with type `Null` in ' |
| 'a non-nullable context', |
| kind: NullabilityFixKind.noValidMigrationForNull); |
| |
| /// Informative message: a null-aware access won't be necessary in strong |
| /// checking mode. |
| static const nullAwarenessUnnecessaryInStrongMode = |
| NullabilityFixDescription._( |
| appliedMessage: |
| 'Null-aware access will be unnecessary in strong checking mode', |
| kind: NullabilityFixKind.nullAwarenessUnnecessaryInStrongMode); |
| |
| /// Informative message: a null-aware assignment won't be necessary in strong |
| /// checking mode. |
| static const nullAwareAssignmentUnnecessaryInStrongMode = |
| NullabilityFixDescription._( |
| appliedMessage: |
| 'Null-aware assignment will be unnecessary in strong checking mode', |
| kind: NullabilityFixKind.nullAwareAssignmentUnnecessaryInStrongMode); |
| |
| static const otherCastExpression = NullabilityFixDescription._( |
| appliedMessage: 'Added a cast to an expression (non-downcast)', |
| kind: NullabilityFixKind.otherCastExpression, |
| ); |
| |
| /// An unnecessary downcast has been discarded. |
| static const removeLanguageVersionComment = NullabilityFixDescription._( |
| appliedMessage: 'Removed language version comment so that null safety ' |
| 'features will be allowed in this file', |
| kind: NullabilityFixKind.removeLanguageVersionComment, |
| ); |
| |
| /// An unnecessary downcast has been discarded. |
| static const removeAs = NullabilityFixDescription._( |
| appliedMessage: 'Discarded a downcast that is now unnecessary', |
| kind: NullabilityFixKind.removeAs, |
| ); |
| |
| /// A null-aware operator needs to be changed into its non-null-aware |
| /// equivalent. |
| static const removeNullAwareness = NullabilityFixDescription._( |
| appliedMessage: |
| 'Changed a null-aware access into an ordinary access, because the target cannot be null', |
| kind: NullabilityFixKind.removeDeadCode); |
| |
| /// A null-aware assignment was removed because its LHS is non-nullable. |
| static const removeNullAwareAssignment = NullabilityFixDescription._( |
| appliedMessage: |
| 'Removed a null-aware assignment, because the target cannot be null', |
| kind: NullabilityFixKind.removeDeadCode); |
| |
| /// A built_value `@nullable` annotation has been discarded. |
| static const removeNullableAnnotation = NullabilityFixDescription._( |
| appliedMessage: 'Discarded a use of the built_value annotation @nullable', |
| kind: NullabilityFixKind.removeNullableAnnotation, |
| ); |
| |
| /// A message used to indicate a fix has been applied. |
| final String appliedMessage; |
| |
| /// The kind of fix described. |
| final NullabilityFixKind kind; |
| |
| /// A formal parameter needs to have a required keyword added. |
| factory NullabilityFixDescription.addRequired( |
| String? className, String? functionName, String paramName) { |
| var paramContainerName = |
| className == null ? functionName! : "'$className.$functionName'"; |
| return NullabilityFixDescription._( |
| appliedMessage: "Add 'required' keyword to parameter '$paramName' in " |
| '$paramContainerName', |
| kind: NullabilityFixKind.addRequired, |
| ); |
| } |
| |
| /// An explicit type needs to be added. |
| factory NullabilityFixDescription.addType(String typeText) => |
| NullabilityFixDescription._( |
| appliedMessage: "Add the explicit type '$typeText'", |
| kind: NullabilityFixKind.replaceVar, |
| ); |
| |
| /// A method call was changed from calling one method to another. |
| factory NullabilityFixDescription.changeMethodName( |
| String oldName, String newName) => |
| NullabilityFixDescription._( |
| appliedMessage: "Changed method '$oldName' to '$newName'", |
| kind: NullabilityFixKind.changeMethodName); |
| |
| /// An explicit type mentioned in the source program needs to be made |
| /// nullable. |
| factory NullabilityFixDescription.makeTypeNullable(String type) => |
| NullabilityFixDescription._( |
| appliedMessage: "Changed type '$type' to be nullable", |
| kind: NullabilityFixKind.makeTypeNullable, |
| ); |
| |
| /// An explicit type mentioned in the source program will be made |
| /// nullable due to a nullability hint. |
| factory NullabilityFixDescription.makeTypeNullableDueToHint(String type) => |
| NullabilityFixDescription._( |
| appliedMessage: |
| "Changed type '$type' to be nullable, due to a nullability hint", |
| kind: NullabilityFixKind.makeTypeNullableDueToHint, |
| ); |
| |
| /// A 'var' declaration needs to be replaced with an explicit type. |
| factory NullabilityFixDescription.replaceVar(String typeText) => |
| NullabilityFixDescription._( |
| appliedMessage: "Replace 'var' with '$typeText'", |
| kind: NullabilityFixKind.replaceVar, |
| ); |
| |
| /// An explicit type mentioned in the source program does not need to be made |
| /// nullable. |
| factory NullabilityFixDescription.typeNotMadeNullable(String type) => |
| NullabilityFixDescription._( |
| appliedMessage: "Type '$type' was not made nullable", |
| kind: NullabilityFixKind.typeNotMadeNullable, |
| ); |
| |
| /// An explicit type mentioned in the source program does not need to be made |
| /// nullable. |
| factory NullabilityFixDescription.typeNotMadeNullableDueToHint(String type) => |
| NullabilityFixDescription._( |
| appliedMessage: "Type '$type' was not made nullable due to a hint", |
| kind: NullabilityFixKind.typeNotMadeNullableDueToHint, |
| ); |
| |
| const NullabilityFixDescription._( |
| {required this.appliedMessage, required this.kind}); |
| |
| @override |
| int get hashCode => Object.hash(appliedMessage, kind); |
| |
| @override |
| bool operator ==(Object other) => |
| other is NullabilityFixDescription && |
| appliedMessage == other.appliedMessage && |
| kind == other.kind; |
| |
| @override |
| String toString() => |
| 'NullabilityFixDescription(${json.encode(appliedMessage)}, $kind)'; |
| } |
| |
| /// An enumeration of the various kinds of nullability fixes. |
| enum NullabilityFixKind { |
| addThen, |
| addImport, |
| addLate, |
| addLateDueToHint, |
| addLateDueToTestSetup, |
| addLateFinalDueToHint, |
| addRequired, |
| addType, |
| changeMethodName, |
| checkExpression, |
| checkExpressionDueToHint, |
| compoundAssignmentHasNullableSource, |
| compoundAssignmentHasBadCombinedType, |
| conditionFalseInStrongMode, |
| conditionTrueInStrongMode, |
| downcastExpression, |
| makeTypeNullable, |
| makeTypeNullableDueToHint, |
| noValidMigrationForNull, |
| nullAwarenessUnnecessaryInStrongMode, |
| nullAwareAssignmentUnnecessaryInStrongMode, |
| otherCastExpression, |
| removeAs, |
| removeDeadCode, |
| removeLanguageVersionComment, |
| removeNullableAnnotation, |
| replaceVar, |
| typeNotMadeNullable, |
| typeNotMadeNullableDueToHint, |
| } |
| |
| /// Provisional API for DartFix to perform nullability migration. |
| /// |
| /// Usage: pass each input source file to [prepareInput]. Then pass each input |
| /// source file to [processInput]. Then pass each input source file to |
| /// [finalizeInput]. Then call [finish] to obtain the modifications that need |
| /// to be made to each source file. |
| abstract class NullabilityMigration { |
| /// Prepares to perform nullability migration. |
| /// |
| /// If [permissive] is `true`, exception handling logic will try to proceed |
| /// as far as possible even though the migration algorithm is not yet |
| /// complete. TODO(paulberry): remove this mode once the migration algorithm |
| /// is fully implemented. |
| /// |
| /// Optional parameter [removeViaComments] indicates whether code that the |
| /// migration tool wishes to remove should instead be commenting it out. |
| /// |
| /// Optional parameter [warnOnWeakCode] indicates whether weak-only code |
| /// should be warned about or removed (in the way specified by |
| /// [removeViaComments]). |
| factory NullabilityMigration(NullabilityMigrationListener? listener, |
| {bool? permissive, |
| NullabilityMigrationInstrumentation? instrumentation, |
| bool? removeViaComments, |
| bool? warnOnWeakCode}) = NullabilityMigrationImpl; |
| |
| /// Check if this migration is being run permissively. |
| bool? get isPermissive; |
| |
| /// Use this getter after any calls to [prepareInput] to obtain a list of URIs |
| /// of unmigrated dependencies. Ideally, this list should be empty before the |
| /// user tries to migrate their package. |
| List<String> get unmigratedDependencies; |
| |
| void finalizeInput(ResolvedUnitResult result); |
| |
| /// Finishes the migration. Returns a map indicating packages that have been |
| /// newly imported by the migration; the caller should ensure that these |
| /// packages are properly imported by the package's pubspec. |
| /// |
| /// Keys of the returned map are package names; values indicate the minimum |
| /// required version of each package. |
| Map<String, Version> finish(); |
| |
| void prepareInput(ResolvedUnitResult result); |
| |
| void processInput(ResolvedUnitResult result); |
| |
| /// Update the migration after an edge has been added or removed. |
| void update(); |
| } |
| |
| /// [NullabilityMigrationListener] is used by [NullabilityMigration] |
| /// to communicate source changes or "fixes" to the client. |
| abstract class NullabilityMigrationListener { |
| /// [addEdit] is called once for each source edit, in the order in which they |
| /// appear in the source file. |
| void addEdit(Source source, SourceEdit edit); |
| |
| void addSuggestion(String descriptions, Location location); |
| |
| /// [reportException] is called once for each exception that occurs in |
| /// "permissive mode", reporting the location of the exception and the |
| /// exception details. |
| void reportException( |
| Source? source, AstNode? node, Object exception, StackTrace stackTrace); |
| } |