Add navigation targets to the containing unit info

Change-Id: I5d4707f019254e9b338eae23bb8a9fb61ef14e54
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119085
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2019-09-27 13:57:13 +00:00 committed by commit-bot@chromium.org
parent 30b2a5dbb3
commit 6eaebb8463
3 changed files with 90 additions and 52 deletions

View file

@ -35,6 +35,10 @@ class InfoBuilder {
/// The listener used to gather the changes to be applied.
final DartFixListener listener;
/// A map from the path of a compilation unit to the information about that
/// unit.
final Map<String, UnitInfo> unitMap = {};
/// Initialize a newly created builder.
InfoBuilder(this.info, this.listener);
@ -112,46 +116,47 @@ class InfoBuilder {
/// Return the migration information for the given unit.
UnitInfo _explainUnit(SourceInformation sourceInfo, ParsedUnitResult result,
SourceFileEdit fileEdit) {
List<RegionInfo> regions = [];
UnitInfo unitInfo = _unitForPath(result.path);
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);
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));
if (fileEdit != null) {
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);
}
regions.add(
RegionInfo(end + delta, replacement.length, explanation, details));
// 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);
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(end + delta, replacement.length, explanation, details));
}
regions.sort((first, second) => first.offset.compareTo(second.offset));
}
regions.sort((first, second) => first.offset.compareTo(second.offset));
return UnitInfo(result.path, content, regions);
unitInfo.content = content;
return unitInfo;
}
/// Return information about the fix that was applied at the given [offset],
@ -168,8 +173,19 @@ class InfoBuilder {
return null;
}
/// Return the navigation target
NavigationTarget _targetFor(EdgeOriginInfo origin) {
AstNode node = origin.node;
return NavigationTarget(origin.source.fullName, node.offset, node.length);
String filePath = origin.source.fullName;
UnitInfo unitInfo = _unitForPath(filePath);
NavigationTarget target =
NavigationTarget(filePath, node.offset, node.length);
unitInfo.targets.add(target);
return target;
}
/// Return the unit info for the file at the given [path].
UnitInfo _unitForPath(String path) {
return unitMap.putIfAbsent(path, () => UnitInfo(path));
}
}

View file

@ -2,6 +2,8 @@
// 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/src/generated/utilities_general.dart';
/// The migration information associated with a single library.
class LibraryInfo {
/// The information about the units in the library. The information about the
@ -24,7 +26,18 @@ class NavigationTarget {
final int length;
/// Initialize a newly created anchor.
const NavigationTarget(this.filePath, this.offset, this.length);
NavigationTarget(this.filePath, this.offset, this.length);
@override
int get hashCode => JenkinsSmiHash.hash3(filePath.hashCode, offset, length);
@override
bool operator ==(other) {
return other is NavigationTarget &&
other.filePath == filePath &&
other.offset == offset &&
other.length == length;
}
}
/// An additional detail related to a region.
@ -65,12 +78,15 @@ class UnitInfo {
final String path;
/// The content of unit.
final String content;
String content;
/// The information about the regions that have an explanation associated with
/// them.
final List<RegionInfo> regions;
final List<RegionInfo> regions = [];
/// The navigation targets that are located in this file.
final Set<NavigationTarget> targets = {};
/// Initialize a newly created unit.
UnitInfo(this.path, this.content, this.regions);
UnitInfo(this.path);
}

View file

@ -19,12 +19,12 @@ main() {
class InstrumentationRendererTest extends AbstractAnalysisTest {
test_outputContainsEachPath() async {
LibraryInfo info = LibraryInfo([
UnitInfo('/lib/a.dart', 'int? a = null;',
[RegionInfo(3, 1, 'null was assigned', [])]),
UnitInfo('/lib/part1.dart', 'int? b = null;',
[RegionInfo(3, 1, 'null was assigned', [])]),
UnitInfo('/lib/part2.dart', 'int? c = null;',
[RegionInfo(3, 1, 'null was assigned', [])]),
unit('/lib/a.dart', 'int? a = null;',
regions: [RegionInfo(3, 1, 'null was assigned', [])]),
unit('/lib/part1.dart', 'int? b = null;',
regions: [RegionInfo(3, 1, 'null was assigned', [])]),
unit('/lib/part2.dart', 'int? c = null;',
regions: [RegionInfo(3, 1, 'null was assigned', [])]),
]);
String output = InstrumentationRenderer(info).render();
expect(output, contains('<h2>/lib/a.dart</h2>'));
@ -34,8 +34,8 @@ class InstrumentationRendererTest extends AbstractAnalysisTest {
test_outputContainsEscapedHtml() async {
LibraryInfo info = LibraryInfo([
UnitInfo('/lib/a.dart', 'List<String>? a = null;',
[RegionInfo(12, 1, 'null was assigned', [])]),
unit('/lib/a.dart', 'List<String>? a = null;',
regions: [RegionInfo(12, 1, 'null was assigned', [])]),
]);
String output = InstrumentationRenderer(info).render();
expect(
@ -46,7 +46,7 @@ class InstrumentationRendererTest extends AbstractAnalysisTest {
test_outputContainsEscapedHtml_ampersand() async {
LibraryInfo info = LibraryInfo([
UnitInfo('/lib/a.dart', 'bool a = true && false;', []),
unit('/lib/a.dart', 'bool a = true && false;', regions: []),
]);
String output = InstrumentationRenderer(info).render();
expect(output, contains('bool a = true &amp;&amp; false;'));
@ -54,8 +54,8 @@ class InstrumentationRendererTest extends AbstractAnalysisTest {
test_outputContainsModifiedAndUnmodifiedRegions() async {
LibraryInfo info = LibraryInfo([
UnitInfo('/lib/a.dart', 'int? a = null;',
[RegionInfo(3, 1, 'null was assigned', [])]),
unit('/lib/a.dart', 'int? a = null;',
regions: [RegionInfo(3, 1, 'null was assigned', [])]),
]);
String output = InstrumentationRenderer(info).render();
expect(
@ -63,4 +63,10 @@ class InstrumentationRendererTest extends AbstractAnalysisTest {
contains('int<span class="region">?'
'<span class="tooltip">null was assigned</span></span> a = null;'));
}
UnitInfo unit(String path, String content, {List<RegionInfo> regions}) {
return UnitInfo(path)
..content = content
..regions.addAll(regions);
}
}