Skip to content

Commit 07db94d

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Add support for details and implement some initial details for regions
Details provide a way for clients to provide navigation support. I'm imagining that the description could be used as link text. The deatils are currently more of a data dump than information. I need to see more migration results in order to figure out when and how to pare down the data into actionable information. We might want to capture all of the navigation targets as we build the info so that we can create anchors in the HTML. Change-Id: Ie48622ce7d935d3c028ea42e51cfc8da590db149 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/118908 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]>
1 parent f3793ab commit 07db94d

File tree

8 files changed

+145
-25
lines changed

8 files changed

+145
-25
lines changed

pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@ analyzer:
214214

215215
/// Generate output into the given [folder].
216216
void _generateOutput(OverlayResourceProvider provider, Folder folder) async {
217-
List<LibraryInfo> libraryInfos = await InfoBuilder(listener.server)
218-
.explainMigration(instrumentationListener.data, listener);
217+
List<LibraryInfo> libraryInfos =
218+
await InfoBuilder(instrumentationListener.data, listener)
219+
.explainMigration();
219220
listener.addDetail('libraryInfos has ${libraryInfos.length} libs');
220221
for (LibraryInfo info in libraryInfos) {
221222
var pathContext = provider.pathContext;

pkg/analysis_server/lib/src/edit/nnbd_migration/info_builder.dart

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,42 @@ import 'package:analysis_server/src/edit/nnbd_migration/instrumentation_informat
88
import 'package:analysis_server/src/edit/nnbd_migration/migration_info.dart';
99
import 'package:analyzer/dart/analysis/results.dart';
1010
import 'package:analyzer/dart/analysis/session.dart';
11+
import 'package:analyzer/dart/ast/ast.dart';
1112
import 'package:analyzer/src/generated/source.dart';
12-
import 'package:analyzer_plugin/protocol/protocol_common.dart';
13+
import 'package:analyzer_plugin/protocol/protocol_common.dart'
14+
show Location, SourceEdit, SourceFileEdit;
15+
import 'package:nnbd_migration/instrumentation.dart';
16+
import 'package:nnbd_migration/nnbd_migration.dart';
17+
18+
class FixInfo {
19+
/// The fix being described.
20+
SingleNullabilityFix fix;
21+
22+
/// The reasons why the fix was made.
23+
List<FixReasonInfo> reasons;
24+
25+
/// Initialize information about a fix from the given map [entry].
26+
FixInfo(this.fix, this.reasons);
27+
}
1328

1429
/// A builder used to build the migration information for a library.
1530
class InfoBuilder {
16-
/// The analysis session used to get information about libraries.
17-
AnalysisServer server;
31+
/// The instrumentation information gathered while the migration engine was
32+
/// running.
33+
final InstrumentationInformation info;
34+
35+
/// The listener used to gather the changes to be applied.
36+
final DartFixListener listener;
1837

1938
/// Initialize a newly created builder.
20-
InfoBuilder(this.server);
39+
InfoBuilder(this.info, this.listener);
40+
41+
/// The analysis server used to get information about libraries.
42+
AnalysisServer get server => listener.server;
2143

2244
/// Return the migration information for all of the libraries that were
2345
/// migrated.
24-
Future<List<LibraryInfo>> explainMigration(
25-
InstrumentationInformation info, DartFixListener listener) async {
46+
Future<List<LibraryInfo>> explainMigration() async {
2647
Map<Source, SourceInformation> sourceInfo = info.sourceInformation;
2748
List<LibraryInfo> libraries = [];
2849
for (Source source in sourceInfo.keys) {
@@ -38,6 +59,42 @@ class InfoBuilder {
3859
return libraries;
3960
}
4061

62+
/// Compute the details for the fix with the given [fixInfo].
63+
List<RegionDetail> _computeDetails(FixInfo fixInfo) {
64+
List<RegionDetail> details = [];
65+
for (FixReasonInfo reason in fixInfo.reasons) {
66+
if (reason is NullabilityNodeInfo) {
67+
for (EdgeInfo edge in reason.upstreamEdges) {
68+
EdgeOriginInfo origin = info.edgeOrigin[edge];
69+
if (origin != null) {
70+
AstNode node = origin.node;
71+
if (node.parent is ArgumentList) {
72+
if (node is NullLiteral) {
73+
details.add(RegionDetail(
74+
'null is explicitly passed as an argument.',
75+
_targetFor(origin)));
76+
} else {
77+
details.add(RegionDetail(
78+
'A nullable value is explicitly passed as an argument.',
79+
_targetFor(origin)));
80+
}
81+
} else {
82+
details.add(RegionDetail(
83+
'A nullable value is assigned.', _targetFor(origin)));
84+
}
85+
}
86+
}
87+
} else if (reason is EdgeInfo) {
88+
// TODO(brianwilkerson) Implement this after finding an example whose
89+
// reason is an edge.
90+
} else {
91+
throw UnimplementedError(
92+
'Unexpected class of reason: ${reason.runtimeType}');
93+
}
94+
}
95+
return details;
96+
}
97+
4198
/// Return the migration information for the given library.
4299
LibraryInfo _explainLibrary(
43100
ParsedLibraryResult result,
@@ -47,13 +104,14 @@ class InfoBuilder {
47104
List<UnitInfo> units = [];
48105
for (ParsedUnitResult unit in result.units) {
49106
SourceFileEdit edit = listener.sourceChange.getFileEdit(unit.path);
50-
units.add(_explainUnit(unit, edit));
107+
units.add(_explainUnit(sourceInfo, unit, edit));
51108
}
52109
return LibraryInfo(units);
53110
}
54111

55112
/// Return the migration information for the given unit.
56-
UnitInfo _explainUnit(ParsedUnitResult result, SourceFileEdit fileEdit) {
113+
UnitInfo _explainUnit(SourceInformation sourceInfo, ParsedUnitResult result,
114+
SourceFileEdit fileEdit) {
57115
List<RegionInfo> regions = [];
58116
String content = result.content;
59117
// [fileEdit] is null when a file has no edits.
@@ -83,14 +141,35 @@ class InfoBuilder {
83141
int delta = deltas[index--];
84142
// Insert the replacement text without deleting the replaced text.
85143
content = content.replaceRange(end, end, replacement);
144+
FixInfo fixInfo = _findFixInfo(sourceInfo, offset);
145+
String explanation = '${fixInfo.fix.description.appliedMessage}.';
146+
List<RegionDetail> details = _computeDetails(fixInfo);
86147
if (length > 0) {
87-
// TODO(brianwilkerson) Create a sensible explanation.
88-
regions.add(RegionInfo(offset + delta, length, 'removed'));
148+
regions.add(RegionInfo(offset + delta, length, explanation, details));
89149
}
90-
// TODO(brianwilkerson) Create a sensible explanation.
91-
regions.add(RegionInfo(end + delta, replacement.length, 'added'));
150+
regions.add(
151+
RegionInfo(end + delta, replacement.length, explanation, details));
92152
}
93153
regions.sort((first, second) => first.offset.compareTo(second.offset));
94154
return UnitInfo(result.path, content, regions);
95155
}
156+
157+
/// Return information about the fix that was applied at the given [offset],
158+
/// or `null` if the information could not be found. The information is
159+
/// extracted from the [sourceInfo].
160+
FixInfo _findFixInfo(SourceInformation sourceInfo, int offset) {
161+
for (MapEntry<SingleNullabilityFix, List<FixReasonInfo>> entry
162+
in sourceInfo.fixes.entries) {
163+
Location location = entry.key.location;
164+
if (location.offset == offset) {
165+
return FixInfo(entry.key, entry.value);
166+
}
167+
}
168+
return null;
169+
}
170+
171+
NavigationTarget _targetFor(EdgeOriginInfo origin) {
172+
AstNode node = origin.node;
173+
return NavigationTarget(origin.source.fullName, node.offset, node.length);
174+
}
96175
}

pkg/analysis_server/lib/src/edit/nnbd_migration/migration_info.dart

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,34 @@ class LibraryInfo {
1212
LibraryInfo(this.units);
1313
}
1414

15+
/// A location to which a user might want to navigate.
16+
class NavigationTarget {
17+
/// The file containing the anchor.
18+
final String filePath;
19+
20+
/// The offset of the anchor.
21+
final int offset;
22+
23+
/// The length of the anchor.
24+
final int length;
25+
26+
/// Initialize a newly created anchor.
27+
const NavigationTarget(this.filePath, this.offset, this.length);
28+
}
29+
30+
/// An additional detail related to a region.
31+
class RegionDetail {
32+
/// A textual description of the detail.
33+
final String description;
34+
35+
/// The location associated with the detail, such as the location of an
36+
/// argument that's assigned to a parameter.
37+
final NavigationTarget target;
38+
39+
/// Initialize a newly created detail.
40+
RegionDetail(this.description, this.target);
41+
}
42+
1543
/// A description of an explanation associated with a region of code that was
1644
/// modified.
1745
class RegionInfo {
@@ -24,8 +52,11 @@ class RegionInfo {
2452
/// The explanation to be displayed for the region.
2553
final String explanation;
2654

55+
/// Details that further explain why a change was made.
56+
final List<RegionDetail> details;
57+
2758
/// Initialize a newly created region.
28-
RegionInfo(this.offset, this.length, this.explanation);
59+
RegionInfo(this.offset, this.length, this.explanation, this.details);
2960
}
3061

3162
/// The migration information associated with a single compilation unit.

pkg/analysis_server/test/edit/nnbd_migration/instrumentation_output_test.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ class InstrumentationRendererTest extends AbstractAnalysisTest {
2020
test_outputContainsEachPath() async {
2121
LibraryInfo info = LibraryInfo([
2222
UnitInfo('/lib/a.dart', 'int? a = null;',
23-
[RegionInfo(3, 1, 'null was assigned')]),
23+
[RegionInfo(3, 1, 'null was assigned', [])]),
2424
UnitInfo('/lib/part1.dart', 'int? b = null;',
25-
[RegionInfo(3, 1, 'null was assigned')]),
25+
[RegionInfo(3, 1, 'null was assigned', [])]),
2626
UnitInfo('/lib/part2.dart', 'int? c = null;',
27-
[RegionInfo(3, 1, 'null was assigned')]),
27+
[RegionInfo(3, 1, 'null was assigned', [])]),
2828
]);
2929
String output = InstrumentationRenderer(info).render();
3030
expect(output, contains('<h2>/lib/a.dart</h2>'));
@@ -35,7 +35,7 @@ class InstrumentationRendererTest extends AbstractAnalysisTest {
3535
test_outputContainsEscapedHtml() async {
3636
LibraryInfo info = LibraryInfo([
3737
UnitInfo('/lib/a.dart', 'List<String>? a = null;',
38-
[RegionInfo(12, 1, 'null was assigned')]),
38+
[RegionInfo(12, 1, 'null was assigned', [])]),
3939
]);
4040
String output = InstrumentationRenderer(info).render();
4141
expect(
@@ -55,7 +55,7 @@ class InstrumentationRendererTest extends AbstractAnalysisTest {
5555
test_outputContainsModifiedAndUnmodifiedRegions() async {
5656
LibraryInfo info = LibraryInfo([
5757
UnitInfo('/lib/a.dart', 'int? a = null;',
58-
[RegionInfo(3, 1, 'null was assigned')]),
58+
[RegionInfo(3, 1, 'null was assigned', [])]),
5959
]);
6060
String output = InstrumentationRenderer(info).render();
6161
expect(

pkg/analysis_server/test/src/edit/nnbd_migration/info_builder_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ class InfoBuilderTest extends AbstractAnalysisTest {
4848
migration.processInput(result);
4949
migration.finish();
5050
// Build the migration info.
51-
InfoBuilder builder = InfoBuilder(server);
5251
InstrumentationInformation info = instrumentationListener.data;
53-
infos = await builder.explainMigration(info, listener);
52+
InfoBuilder builder = InfoBuilder(info, listener);
53+
infos = await builder.explainMigration();
5454
}
5555

5656
test_parameter_nullableFromInvocation() async {

pkg/nnbd_migration/lib/instrumentation.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ abstract class NullabilityNodeInfo implements FixReasonInfo {
179179
/// After migration is complete, this getter can be used to query whether
180180
/// the type associated with this node was determined to be nullable.
181181
bool get isNullable;
182+
183+
/// The edges that caused this node to have the nullability that it has.
184+
Iterable<EdgeInfo> get upstreamEdges;
182185
}
183186

184187
/// Information exposed to the migration client about a single step in the

pkg/nnbd_migration/lib/nnbd_migration.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ class NullabilityFixDescription {
3636
factory NullabilityFixDescription.addRequired(
3737
String className, String functionName, String paramName) =>
3838
NullabilityFixDescription._(
39-
appliedMessage: "Add 'required' keyword to parameter $paramName in " +
40-
(className == null ? functionName : '$className.$functionName'));
39+
appliedMessage:
40+
"Add 'required' keyword to parameter '$paramName' in " +
41+
(className == null
42+
? functionName
43+
: "'$className.$functionName'"));
4144

4245
/// An explicit type mentioned in the source program needs to be made
4346
/// nullable.
4447
factory NullabilityFixDescription.makeTypeNullable(String type) =>
4548
NullabilityFixDescription._(
46-
appliedMessage: 'Changed type $type to be nullable',
49+
appliedMessage: "Changed type '$type' to be nullable",
4750
);
4851

4952
const NullabilityFixDescription._({@required this.appliedMessage});

pkg/nnbd_migration/lib/src/nullability_node.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,9 @@ abstract class NullabilityNode implements NullabilityNodeInfo {
500500
/// nullability migration needs to decide whether it is optional or required.
501501
bool get isPossiblyOptional => _isPossiblyOptional;
502502

503+
@override
504+
Iterable<EdgeInfo> get upstreamEdges => _upstreamEdges;
505+
503506
String get _debugPrefix;
504507

505508
NullabilityState get _state;

0 commit comments

Comments
 (0)