Add a way to map navigation target offsets to the edited content

Change-Id: Ia5e10cbc1a3510e6764fa67e5e6d47c9f19dbc48
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119165
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2019-09-27 16:52:24 +00:00 committed by commit-bot@chromium.org
parent 605422c94e
commit 0829212a2a
5 changed files with 142 additions and 18 deletions

View file

@ -6,6 +6,7 @@ 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:analysis_server/src/edit/nnbd_migration/offset_mapper.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
@ -123,17 +124,7 @@ class InfoBuilder {
List<RegionInfo> regions = unitInfo.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);
}
OffsetMapper mapper = OffsetMapper.forEdits(edits);
// Apply edits in reverse order and build the regions.
int index = edits.length - 1;
for (SourceEdit edit in edits.reversed) {
@ -141,19 +132,20 @@ class InfoBuilder {
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);
FixInfo fixInfo = _findFixInfo(sourceInfo, offset);
String explanation = '${fixInfo.fix.description.appliedMessage}.';
List<RegionDetail> details = _computeDetails(fixInfo);
if (length > 0) {
regions.add(RegionInfo(offset + delta, length, explanation, details));
regions.add(
RegionInfo(mapper.map(offset), length, explanation, details));
}
regions.add(
RegionInfo(end + delta, replacement.length, explanation, details));
regions.add(RegionInfo(
mapper.map(end), replacement.length, explanation, details));
}
regions.sort((first, second) => first.offset.compareTo(second.offset));
unitInfo.offsetMapper = mapper;
}
unitInfo.content = content;
return unitInfo;
@ -173,7 +165,7 @@ class InfoBuilder {
return null;
}
/// Return the navigation target
/// Return the navigation target corresponding to the given [origin].
NavigationTarget _targetFor(EdgeOriginInfo origin) {
AstNode node = origin.node;
String filePath = origin.source.fullName;

View file

@ -2,6 +2,7 @@
// 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/edit/nnbd_migration/offset_mapper.dart';
import 'package:analyzer/src/generated/utilities_general.dart';
/// The migration information associated with a single library.
@ -81,12 +82,17 @@ class UnitInfo {
String content;
/// The information about the regions that have an explanation associated with
/// them.
/// them. The offsets in these regions are offsets into the post-edit content.
final List<RegionInfo> regions = [];
/// The navigation targets that are located in this file.
/// The navigation targets that are located in this file. The offsets in these
/// targets are offsets into the pre-edit content.
final Set<NavigationTarget> targets = {};
/// The object used to map the pre-edit offsets in the navigation targets to
/// the post-edit offsets in the [content].
OffsetMapper offsetMapper = OffsetMapper.identity;
/// Initialize a newly created unit.
UnitInfo(this.path);
}

View file

@ -0,0 +1,73 @@
// 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:analyzer_plugin/protocol/protocol_common.dart';
/// An object that can map the offsets before a sequence of edits to the offsets
/// after applying the edits.
abstract class OffsetMapper {
/// A mapper used for files that were not modified.
static OffsetMapper identity = _IdentityMapper();
/// Return a mapper representing the file modified by the given [edits].
factory OffsetMapper.forEdits(List<SourceEdit> edits) => _EditMapper(edits);
/// Return the post-edit offset that corresponds to the given pre-edit
/// [offset].
int map(int offset);
}
/// A mapper used for files that were modified by a set of edits.
class _EditMapper implements OffsetMapper {
/// A list whose elements are the highest pre-edit offset for which the
/// corresponding element of [_deltas] should be applied.
List<int> _offsets = [];
/// A list whose elements are the deltas to be applied for all pre-edit
/// offsets that are less than or equal to the corresponding element of
/// [_offsets].
List<int> _deltas = [];
/// Initialize a newly created mapper based on the given set of [edits].
_EditMapper(List<SourceEdit> edits) {
_initializeDeltas(edits);
}
@override
int map(int offset) => offset + _deltaFor(offset);
/// Return the delta to be added to the pre-edit [offset] to produce the
/// post-edit offset.
int _deltaFor(int offset) {
for (int i = 0; i < _offsets.length; i++) {
int currentOffset = _offsets[i];
if (currentOffset >= offset || currentOffset < 0) {
return _deltas[i];
}
}
// We should never get here because [_initializeDeltas] always adds an
// offset/delta pair at the end of the list whose offset is less than zero.
return 0;
}
/// Initialize the list of old offsets and deltas used by [_deltaFor].
void _initializeDeltas(List<SourceEdit> edits) {
int previousDelta = 0;
for (SourceEdit edit in edits) {
int offset = edit.offset;
int length = edit.length;
_offsets.add(offset);
_deltas.add(previousDelta);
previousDelta += (edit.replacement.length - length);
}
_offsets.add(-1);
_deltas.add(previousDelta);
}
}
/// A mapper used for files that were not modified.
class _IdentityMapper implements OffsetMapper {
@override
int map(int offset) => offset;
}

View file

@ -0,0 +1,51 @@
// 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/edit/nnbd_migration/offset_mapper.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../analysis_abstract.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(OffsetMapperTest);
});
}
@reflectiveTest
class OffsetMapperTest extends AbstractAnalysisTest {
test_identity() {
OffsetMapper mapper = OffsetMapper.identity;
expect(mapper.map(0), 0);
expect(mapper.map(20), 20);
expect(mapper.map(0xFFFFFF), 0xFFFFFF);
}
test_multipleEdits() {
OffsetMapper mapper = OffsetMapper.forEdits([
SourceEdit(13, 0, '?'),
SourceEdit(21, 0, '!'),
SourceEdit(32, 0, '?'),
]);
expect(mapper.map(0), 0);
expect(mapper.map(13), 13);
expect(mapper.map(14), 15);
expect(mapper.map(21), 22);
expect(mapper.map(22), 24);
expect(mapper.map(32), 34);
expect(mapper.map(33), 36);
expect(mapper.map(55), 58);
}
test_singleEdit() {
OffsetMapper mapper = OffsetMapper.forEdits([
SourceEdit(13, 0, '?'),
]);
expect(mapper.map(0), 0);
expect(mapper.map(13), 13);
expect(mapper.map(14), 15);
}
}

View file

@ -5,9 +5,11 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'info_builder_test.dart' as info_builder;
import 'offset_mapper_test.dart' as offset_mapper;
main() {
defineReflectiveSuite(() {
info_builder.main();
offset_mapper.main();
}, name: 'nnbd_migration');
}