blob: 3fa023b439692dab3c0367424dad14af9678b5a3 [file] [log] [blame]
// 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 'package:analysis_server/src/nullability/decorated_type.dart'
as analyzer;
import 'package:analysis_server/src/nullability/expression_checks.dart'
as analyzer;
import 'package:analysis_server/src/nullability/transitional_api.dart'
as analyzer;
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:meta/meta.dart';
export 'package:analysis_server/src/nullability/transitional_api.dart'
show NamedNoDefaultParameterHeuristic, NullabilityMigrationAssumptions;
/// Kinds of fixes that might be performed by nullability migration.
class NullabilityFixKind {
/// A formal parameter needs to have a required annotation added.
static const addRequired =
const NullabilityFixKind._(appliedMessage: 'Add a required annotation');
/// An expression's value needs to be null-checked.
static const checkExpression = const NullabilityFixKind._(
appliedMessage: 'Added a null check to an expression',
);
/// An explicit type mentioned in the source program needs to be made
/// nullable.
static const makeTypeNullable = const NullabilityFixKind._(
appliedMessage: 'Changed a type to be nullable',
);
/// An if-test or conditional expression needs to have its "then" branch
/// discarded.
static const discardThen = const NullabilityFixKind._(
appliedMessage: 'Discarded an unreachable conditional then branch',
);
/// An if-test or conditional expression needs to have its "else" branch
/// discarded.
static const discardElse = const NullabilityFixKind._(
appliedMessage: 'Discarded an unreachable conditional else branch',
);
/// A message used by dartfix to indicate a fix has been applied.
final String appliedMessage;
const NullabilityFixKind._({@required this.appliedMessage});
}
/// 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 call [finish] to obtain the
/// modifications that need to be made to each source file.
///
/// TODO(paulberry): figure out whether this API is what we want, and figure out
/// what file/folder it belongs in.
class NullabilityMigration {
final analyzer.NullabilityMigration _analyzerMigration;
final NullabilityMigrationListener listener;
/// 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.
NullabilityMigration(this.listener,
{bool permissive: false,
analyzer.NullabilityMigrationAssumptions assumptions:
const analyzer.NullabilityMigrationAssumptions()})
: _analyzerMigration = analyzer.NullabilityMigration(
permissive: permissive, assumptions: assumptions);
void finish() {
for (var entry in _analyzerMigration.finish().entries) {
var source = entry.key;
for (var potentialModification in entry.value) {
var fix = _SingleNullabilityFix(source, potentialModification);
listener.addFix(fix);
for (var edit in potentialModification.modifications) {
listener.addEdit(fix, edit);
}
}
}
}
void prepareInput(ResolvedUnitResult result) {
_analyzerMigration.prepareInput(result.unit);
}
void processInput(ResolvedUnitResult result) {
_analyzerMigration.processInput(result.unit, result.typeProvider);
}
}
/// [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(SingleNullabilityFix fix, SourceEdit edit);
/// [addFix] is called once for each source change.
void addFix(SingleNullabilityFix fix);
}
/// Representation of a single conceptual change made by the nullability
/// migration algorithm. This change might require multiple source edits to
/// achieve.
abstract class SingleNullabilityFix {
/// What kind of fix this is.
NullabilityFixKind get kind;
/// Location of the change, for reporting to the user.
Location get location;
/// File to change.
Source get source;
}
/// Implementation of [SingleNullabilityFix] used internally by
/// [NullabilityMigration].
class _SingleNullabilityFix extends SingleNullabilityFix {
@override
final Source source;
@override
final NullabilityFixKind kind;
factory _SingleNullabilityFix(
Source source, analyzer.PotentialModification potentialModification) {
// TODO(paulberry): once everything is migrated into the analysis server,
// the migration engine can just create SingleNullabilityFix objects
// directly and set their kind appropriately; we won't need to translate the
// kinds using a bunch of `is` checks.
NullabilityFixKind kind;
if (potentialModification is analyzer.ExpressionChecks) {
kind = NullabilityFixKind.checkExpression;
} else if (potentialModification is analyzer.DecoratedTypeAnnotation) {
kind = NullabilityFixKind.makeTypeNullable;
} else if (potentialModification is analyzer.ConditionalModification) {
kind = potentialModification.discard.keepFalse.value
? NullabilityFixKind.discardThen
: NullabilityFixKind.discardElse;
} else if (potentialModification is analyzer.PotentiallyAddRequired) {
kind = NullabilityFixKind.addRequired;
} else {
throw new UnimplementedError('TODO(paulberry)');
}
return _SingleNullabilityFix._(source, kind);
}
_SingleNullabilityFix._(this.source, this.kind);
/// TODO(paulberry): do something better
Location get location => null;
}