blob: acd9c24ec5780784952767356570ae4e1e8d21fa [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/analysis_server.dart';
import 'package:analysis_server/src/edit/fix/dartfix_listener.dart';
import 'package:analysis_server/src/edit/nnbd_migration/instrumentation_information.dart';
import 'package:analysis_server/src/edit/nnbd_migration/migration_info.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
/// A builder used to build the migration information for a library.
class InfoBuilder {
/// The analysis session used to get information about libraries.
AnalysisServer server;
/// Initialize a newly created builder.
InfoBuilder(this.server);
/// Return the migration information for all of the libraries that were
/// migrated.
Future<List<LibraryInfo>> explainMigration(
InstrumentationInformation info, DartFixListener listener) async {
Map<Source, SourceInformation> sourceInfo = info.sourceInformation;
List<LibraryInfo> libraries = [];
for (Source source in sourceInfo.keys) {
String filePath = source.fullName;
AnalysisSession session =
server.getAnalysisDriver(filePath).currentSession;
if (!session.getFile(filePath).isPart) {
ParsedLibraryResult result = await session.getParsedLibrary(filePath);
libraries
.add(_explainLibrary(result, info, sourceInfo[source], listener));
}
}
return libraries;
}
/// Return the migration information for the given library.
LibraryInfo _explainLibrary(
ParsedLibraryResult result,
InstrumentationInformation info,
SourceInformation sourceInfo,
DartFixListener listener) {
List<UnitInfo> units = [];
for (ParsedUnitResult unit in result.units) {
SourceFileEdit edit = listener.sourceChange.getFileEdit(unit.path);
units.add(_explainUnit(unit, edit));
}
return LibraryInfo(units);
}
/// Return the migration information for the given unit.
UnitInfo _explainUnit(ParsedUnitResult result, SourceFileEdit fileEdit) {
List<RegionInfo> regions = [];
String content = result.content;
// [fileEdit] is null when a file has no edits.
if (fileEdit == null) {
return UnitInfo(result.path, content, regions);
}
List<SourceEdit> edits = fileEdit.edits;
edits.sort((first, second) => first.offset.compareTo(second.offset));
// Compute the deltas for the regions that will be computed as we apply the
// edits. We need the deltas because the offsets to the regions are relative
// to the edited source, but the edits are being applied in reverse order so
// the offset in the pre-edited source will not match the offset in the
// post-edited source. The deltas compensate for that difference.
List<int> deltas = [];
int previousDelta = 0;
for (SourceEdit edit in edits) {
deltas.add(previousDelta);
previousDelta += (edit.replacement.length - edit.length);
}
// Apply edits in reverse order and build the regions.
int index = edits.length - 1;
for (SourceEdit edit in edits.reversed) {
int offset = edit.offset;
int length = edit.length;
String replacement = edit.replacement;
int end = offset + length;
int delta = deltas[index--];
// Insert the replacement text without deleting the replaced text.
content = content.replaceRange(end, end, replacement);
if (length > 0) {
// TODO(brianwilkerson) Create a sensible explanation.
regions.add(RegionInfo(offset + delta, length, 'removed'));
}
// TODO(brianwilkerson) Create a sensible explanation.
regions.add(RegionInfo(end + delta, replacement.length, 'added'));
}
regions.sort((first, second) => first.offset.compareTo(second.offset));
return UnitInfo(result.path, content, regions);
}
}