Skip to content

Commit 6b4210d

Browse files
jensjohaCommit Queue
authored andcommitted
[analysis server/analyzer] Improvements to completion
Observation 1: The legacy protocol answered completion requests via a `server.resolveForCompletion` call, and the LSP protocol answered completion requests via a `server.getResolvedUnit` call where the idea is that resolving for completion requires less and is therefore faster. Solution: Make the LSP protocol use `server.resolveForCompletion` too. Observation 2: Completion requests often come right after change request making timing important and the `await driver.applyPendingFileChanges()` call done in the analysis server "pushed" the timing making the `server.resolveForCompletion` actually finished after it had already resolved the whole thing. Solution: Don't do that - the driver adds it to the queue of work, the work is done later in `performWork` called from `AnalysisDriverScheduler._run` where `_applyPendingFileChanges` is always called anyway (which is the call that completes the `applyPendingFileChanges`call). Observation 3: If there is no change yet to be processed, a `resolveForCompletion` call is slower than a `getResolvedUnit` because the resolved unit is cached (assuming it's a priority file) and the `resolveForCompletion` call always parses the file again. Solution: Respond to the `resolveForCompletion` call with the resolved unit data if it's available in the cache. The cache is always cleared when changes happen anyway. Benchmarks on this stuff is a bit weird because it's timing related - so while I'd say this is overall just better there are also runs where we get "bad timing" and the runtimes are therefore not better. In an attempt to clear it up I've run the benchmarks 25 times each, and attempted to put the data in two different buckets as needed. *lsp_type_in_big_file of size 16,000* ``` Fully done after last type (ms): Difference at 95.0% confidence -1419.2 +/- 322.074 -16.8866% +/- 3.83225% (Student's t, pooled s = 566.238) Whole typing time (ms): Difference at 95.0% confidence -1418.88 +/- 322.037 -13.9661% +/- 3.16983% (Student's t, pooled s = 566.172) Uncancelled completion response time (ms): Difference at 95.0% confidence -1590.48 +/- 682.753 -28.0194% +/- 12.028% (Student's t, pooled s = 1200.35) ``` The `Uncancelled completion response time` has a big "+/-" so attempting to "good and bad bucketize" it I get: good bucket: ``` Difference at 95.0% confidence -1929.8 +/- 347.712 -35.9528% +/- 6.47797% (Student's t, pooled s = 543.261) ``` bad bucket (though truthfully there wasn't a clear cutoff before): ``` No difference proven at 95.0% confidence ``` which sort of makes sense: If the completion runs before a (new) change starts processing we now `resolveForCompletion` instead which is faster, but if completion runs after the change has started processing we essentially - both before and after - do nothing (except wait for the calculation to finish) because we just load the data from cache. *lsp_type_in_big_file_ask_for_completion, 16,000* ``` Completion #1 (ms): No difference proven at 95.0% confidence ``` Ehh. There's a clear cutoff in the now, so taking the 8 (how the cutoff happens to be) fastest from each I get ``` Difference at 95.0% confidence -1034.12 +/- 75.3617 -37.673% +/- 2.74542% (Student's t, pooled s = 70.2673) ``` Moving on. ``` Completion #2 (ms): Difference at 95.0% confidence -1076.28 +/- 178.446 -37.2601% +/- 6.17769% (Student's t, pooled s = 313.726) ``` here 2 in the "now" has bad timing, removing them from the statistics gives ``` Difference at 95.0% confidence -1182.39 +/- 104.543 -40.9334% +/- 3.6192% (Student's t, pooled s = 179.748) ``` Moving on. ``` Completion #3 (ms): Difference at 95.0% confidence -735.6 +/- 292.553 -25.6471% +/- 10.2% (Student's t, pooled s = 514.336) and removing the 8 bad ones: Difference at 95.0% confidence -1195.69 +/- 99.305 -41.6884% +/- 3.46232% (Student's t, pooled s = 156.306) ``` Continuing like this: ``` Completion #4 (ms): Difference at 95.0% confidence -948.4 +/- 265.704 -32.9887% +/- 9.24214% (Student's t, pooled s = 467.134) and removing the 5 bad ones: Difference at 95.0% confidence -1252.87 +/- 86.5425 -43.5793% +/- 3.01026% (Student's t, pooled s = 143.022) Completion #5 (ms): Difference at 95.0% confidence -1067.88 +/- 199.266 -37.0011% +/- 6.90439% (Student's t, pooled s = 350.329) and removing the 3 bad ones: Difference at 95.0% confidence -1236.76 +/- 69.5776 -42.8527% +/- 2.4108% (Student's t, pooled s = 118.18) ``` Moving on to the "Completion without change" I realize just now that the benchmark for the first entry is broken - it doesn't wait until the previous change has been processed, meaning that in the 3 cases where we got bad timing in "Completion #5 (ms)" we see about the same result as before, but in the 22 other cases we see bad results because it has to wait until the previous change has been processed. For the remaining (2-5) there is no virtually change which makes sense because both before and now it just fetches the resolved unit from cache. *legacy_type_in_big_file_ask_for_completion* ``` Completion #1 (ms): Difference at 95.0% confidence -1817.08 +/- 60.6836 -48.0638% +/- 1.60515% (Student's t, pooled s = 106.688) Completion #2 (ms): Difference at 95.0% confidence -2208.56 +/- 48.4844 -55.4647% +/- 1.21761% (Student's t, pooled s = 85.2403) Completion #3 (ms): Difference at 95.0% confidence -2159.68 +/- 69.4145 -53.0717% +/- 1.70578% (Student's t, pooled s = 122.037) Completion #4 (ms): Difference at 95.0% confidence -2264.44 +/- 73.2112 -53.5455% +/- 1.73117% (Student's t, pooled s = 128.712) Completion #5 (ms): Difference at 95.0% confidence -2147.4 +/- 68.5023 -50.9254% +/- 1.62453% (Student's t, pooled s = 120.434) ``` The first "Completion without change" suffers from the same as before and I will skip it here. ``` Completion without change #2 (ms): Difference at 95.0% confidence -416.28 +/- 28.2024 -61.3512% +/- 4.15646% (Student's t, pooled s = 49.5826) Completion without change #3 (ms): Difference at 95.0% confidence -687.24 +/- 21.9848 -92.6499% +/- 2.96387% (Student's t, pooled s = 38.6514) Completion without change #4 (ms): Difference at 95.0% confidence -637.32 +/- 26.482 -95.2703% +/- 3.95868% (Student's t, pooled s = 46.5579) Completion without change #5 (ms): Difference at 95.0% confidence -702.32 +/- 18.7277 -95.8301% +/- 2.55535% (Student's t, pooled s = 32.925) ``` I don't know why there doesn't appear to be any timing related issues here (maybe sending and receiving the entire big file (in legacy vs in lsp where a small 'diff' is send) taking more time pushes the timing, but I'm guessing) - nor do I know why now "Completion without change #2" is slower (~250 ms) than the subsequent ones (~30 ms). Change-Id: I4c21d658efccbcf197eedb69f466b2942b78c4b9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/457364 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Jens Johansen <[email protected]>
1 parent 2c244b0 commit 6b4210d

File tree

11 files changed

+202
-38
lines changed

11 files changed

+202
-38
lines changed

pkg/analysis_server/lib/src/analysis_server.dart

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1031,8 +1031,10 @@ abstract class AnalysisServer {
10311031
return null;
10321032
}
10331033

1034+
var result = _tryGetCachedResolvedUnitAsResolvedForCompletion(driver, path);
1035+
if (result != null) return result;
1036+
10341037
try {
1035-
await driver.applyPendingFileChanges();
10361038
return await driver.resolveForCompletion(
10371039
path: path,
10381040
offset: offset,
@@ -1044,6 +1046,37 @@ abstract class AnalysisServer {
10441046
return null;
10451047
}
10461048

1049+
Future<ResolvedForCompletionResultImpl?> resolveForCompletionWithLineColumn({
1050+
required String path,
1051+
required int line,
1052+
required int column,
1053+
required OperationPerformanceImpl performance,
1054+
}) async {
1055+
if (!file_paths.isDart(resourceProvider.pathContext, path)) {
1056+
return null;
1057+
}
1058+
1059+
var driver = getAnalysisDriver(path);
1060+
if (driver == null) {
1061+
return null;
1062+
}
1063+
1064+
var result = _tryGetCachedResolvedUnitAsResolvedForCompletion(driver, path);
1065+
if (result != null) return result;
1066+
1067+
try {
1068+
return await driver.resolveForCompletionWithLineColumn(
1069+
path: path,
1070+
line: line,
1071+
column: column,
1072+
performance: performance,
1073+
);
1074+
} catch (e, st) {
1075+
instrumentationService.logException(e, st);
1076+
}
1077+
return null;
1078+
}
1079+
10471080
/// Sends an LSP notification to the client.
10481081
///
10491082
/// The legacy server will wrap LSP notifications inside an
@@ -1097,6 +1130,34 @@ abstract class AnalysisServer {
10971130
await contextManager.dispose();
10981131
await analyticsManager.shutdown();
10991132
}
1133+
1134+
ResolvedForCompletionResultImpl?
1135+
_tryGetCachedResolvedUnitAsResolvedForCompletion(
1136+
AnalysisDriver driver,
1137+
String path,
1138+
) {
1139+
try {
1140+
ResolvedUnitResult? result = driver.getCachedResolvedUnit(path);
1141+
if (result != null) {
1142+
result as ResolvedUnitResultImpl;
1143+
return ResolvedForCompletionResultImpl(
1144+
analysisSession: result.session,
1145+
fileState: result.fileState,
1146+
path: path,
1147+
uri: result.uri,
1148+
exists: result.exists,
1149+
content: result.content,
1150+
lineInfo: result.lineInfo,
1151+
parsedUnit: result.unit,
1152+
unitElement: result.libraryFragment,
1153+
resolvedNodes: [result.unit],
1154+
);
1155+
}
1156+
} catch (e, st) {
1157+
instrumentationService.logException(e, st);
1158+
}
1159+
return null;
1160+
}
11001161
}
11011162

11021163
/// ContextManager callbacks that operate on the base server regardless

pkg/analysis_server/lib/src/cider/completion.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ class CiderCompletionComputer {
7373
}
7474

7575
var analysisSession = resolvedUnit.analysisSession;
76-
var enclosingNode = resolvedUnit.parsedUnit;
7776

7877
var lineInfo = resolvedUnit.lineInfo;
7978
var offset = lineInfo.getOffsetOfLine(line) + column;
@@ -84,7 +83,6 @@ class CiderCompletionComputer {
8483
filePath: resolvedUnit.path,
8584
fileContent: resolvedUnit.content,
8685
libraryFragment: resolvedUnit.unitElement,
87-
enclosingNode: enclosingNode,
8886
offset: offset,
8987
unit: resolvedUnit.parsedUnit,
9088
);

pkg/analysis_server/lib/src/handler/legacy/completion_get_suggestions2.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,13 @@ class CompletionGetSuggestions2Handler extends CompletionHandler
166166
server.recentPerformance.completion.add(completionPerformance);
167167

168168
var analysisSession = resolvedUnit.analysisSession;
169-
var enclosingNode = resolvedUnit.parsedUnit;
170169

171170
var completionRequest = DartCompletionRequest(
172171
analysisSession: analysisSession,
173172
fileState: resolvedUnit.fileState,
174173
filePath: resolvedUnit.path,
175174
fileContent: resolvedUnit.content,
176175
libraryFragment: resolvedUnit.unitElement,
177-
enclosingNode: enclosingNode,
178176
offset: offset,
179177
unit: resolvedUnit.parsedUnit,
180178
dartdocDirectiveInfo: server.getDartdocDirectiveInfoForSession(

pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ import 'package:analysis_server/src/services/snippets/dart_snippet_request.dart'
2525
import 'package:analysis_server/src/services/snippets/snippet_manager.dart';
2626
import 'package:analysis_server/src/utilities/element_location2.dart';
2727
import 'package:analysis_server/src/utilities/extensions/object.dart';
28-
import 'package:analyzer/dart/analysis/results.dart';
2928
import 'package:analyzer/dart/analysis/session.dart';
3029
import 'package:analyzer/dart/ast/ast.dart' as ast;
3130
import 'package:analyzer/source/line_info.dart';
31+
import 'package:analyzer/src/dart/analysis/results.dart';
3232
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
3333
import 'package:analyzer/src/util/performance/operation_performance.dart';
3434
import 'package:analyzer/src/utilities/fuzzy_matcher.dart';
@@ -118,10 +118,28 @@ class CompletionHandler
118118
// To do this, tell the server to lock requests until we have a resolved
119119
// unit and LineInfo.
120120
late ErrorOr<LineInfo> lineInfo;
121-
late ErrorOr<ResolvedUnitResult> unit;
121+
late ErrorOr<ResolvedForCompletionResultImpl> unit;
122122
await server.pauseSchedulerWhile(() async {
123-
unit = await path.mapResult(requireResolvedUnit);
124-
lineInfo = await unit.map(
123+
unit = await message.performance.runAsync('resolveForCompletion', (
124+
performance,
125+
) async {
126+
return await path.mapResult<ResolvedForCompletionResultImpl>((
127+
path,
128+
) async {
129+
var result = await server.resolveForCompletionWithLineColumn(
130+
path: path,
131+
line: pos.line,
132+
column: pos.character,
133+
performance: performance,
134+
);
135+
if (result != null) return success(result);
136+
return server.isAnalyzed(path)
137+
? analysisFailedError(path)
138+
: fileNotAnalyzedError(path);
139+
});
140+
});
141+
142+
lineInfo = unit.map(
125143
// If we don't have a unit, we can still try to obtain the line info from
126144
// the server (this could be because the file is non-Dart, such as YAML or
127145
// another handled by a plugin).
@@ -312,13 +330,17 @@ class CompletionHandler
312330

313331
Future<Iterable<CompletionItem>> _getDartSnippetItems({
314332
required LspClientCapabilities clientCapabilities,
315-
required ResolvedUnitResult unit,
333+
required ResolvedForCompletionResultImpl unit,
316334
required int offset,
317335
required LineInfo lineInfo,
318336
required bool Function(String input) filter,
319337
CompletionItemDefaults? defaults,
320338
}) async {
321-
var request = DartSnippetRequest(unit: unit, offset: offset);
339+
var request = DartSnippetRequest.fromCompletionResult(
340+
unit: unit,
341+
offset: offset,
342+
file: unit.fileState.source.file,
343+
);
322344
var snippetManager = DartSnippetManager();
323345
var snippets = await snippetManager.computeSnippets(
324346
request,
@@ -340,7 +362,7 @@ class CompletionHandler
340362

341363
Future<ErrorOr<_CompletionResults>> _getServerDartItems(
342364
LspClientCapabilities capabilities,
343-
ResolvedUnitResult unit,
365+
ResolvedForCompletionResultImpl unit,
344366
CompletionPerformance completionPerformance,
345367
OperationPerformanceImpl performance,
346368
int offset,
@@ -351,11 +373,19 @@ class CompletionHandler
351373
var useNotImportedCompletions =
352374
suggestFromUnimportedLibraries && capabilities.applyEdit;
353375

354-
var completionRequest = DartCompletionRequest.forResolvedUnit(
355-
resolvedUnit: unit,
376+
var analysisSession = unit.analysisSession;
377+
378+
var completionRequest = DartCompletionRequest(
379+
analysisSession: analysisSession,
380+
fileState: unit.fileState,
381+
filePath: unit.path,
382+
fileContent: unit.content,
383+
libraryFragment: unit.unitElement,
356384
offset: offset,
357-
dartdocDirectiveInfo: server.getDartdocDirectiveInfoFor(unit),
358-
completionPreference: CompletionPreference.replace,
385+
unit: unit.parsedUnit,
386+
dartdocDirectiveInfo: server.getDartdocDirectiveInfoForSession(
387+
analysisSession,
388+
),
359389
);
360390
var target = completionRequest.target;
361391
var targetPrefix = completionRequest.targetPrefix;
@@ -566,9 +596,8 @@ class CompletionHandler
566596
.enableSnippets;
567597
// We can only produce edits with edit builders for files inside
568598
// the root, so skip snippets entirely if not.
569-
var isEditableFile = unit.session.analysisContext.contextRoot.isAnalyzed(
570-
unit.path,
571-
);
599+
var isEditableFile = analysisSession.analysisContext.contextRoot
600+
.isAnalyzed(unit.path);
572601
List<CompletionItem> unrankedResults;
573602
if (capabilities.completionSnippets &&
574603
snippetsEnabled &&

pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,12 @@ class DartCompletionRequest {
305305
required String filePath,
306306
required String fileContent,
307307
required LibraryFragment libraryFragment,
308-
required AstNode enclosingNode,
309308
required int offset,
310309
required CompilationUnit unit,
311310
DartdocDirectiveInfo? dartdocDirectiveInfo,
312311
CompletionPreference completionPreference = CompletionPreference.insert,
313312
}) {
314-
var target = CompletionTarget.forOffset(enclosingNode, offset);
313+
var target = CompletionTarget.forOffset(unit, offset);
315314

316315
var libraryElement = libraryFragment.element;
317316
var featureComputer = FeatureComputer(
@@ -364,7 +363,6 @@ class DartCompletionRequest {
364363
filePath: resolvedUnit.path,
365364
fileContent: resolvedUnit.content,
366365
libraryFragment: resolvedUnit.libraryFragment,
367-
enclosingNode: resolvedUnit.unit,
368366
offset: offset,
369367
unit: resolvedUnit.unit,
370368
dartdocDirectiveInfo: dartdocDirectiveInfo,

pkg/analysis_server/lib/src/services/snippets/dart/main_function.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ class MainFunction extends DartSnippetProducer {
3232
session: request.analysisSession,
3333
defaultEol: utils.endOfLine,
3434
);
35-
36-
var typeProvider = request.unit.typeProvider;
35+
var typeProvider = request.typeProvider;
3736
var listString = typeProvider.listType(typeProvider.stringType);
3837

3938
await builder.addDartFileEdit(request.filePath, (builder) {

pkg/analysis_server/lib/src/services/snippets/dart_snippet_request.dart

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,32 @@ import 'package:analyzer/dart/analysis/results.dart';
77
import 'package:analyzer/dart/analysis/session.dart';
88
import 'package:analyzer/dart/ast/ast.dart';
99
import 'package:analyzer/dart/ast/token.dart';
10+
import 'package:analyzer/dart/element/element.dart';
11+
import 'package:analyzer/dart/element/type_provider.dart';
1012
import 'package:analyzer/file_system/file_system.dart';
1113
import 'package:analyzer/source/source_range.dart';
14+
import 'package:analyzer/src/dart/analysis/results.dart';
1215
import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
1316

1417
/// The information about a request for a list of snippets within a Dart file.
1518
class DartSnippetRequest {
16-
/// The resolved unit for the file that snippets are being requested for.
17-
final ResolvedUnitResult unit;
19+
/// The analysis session that produced the elements of the request.
20+
final AnalysisSession analysisSession;
21+
22+
/// The type provider used when resolving the compilation [unit].
23+
final TypeProvider typeProvider;
24+
25+
/// The file resource.
26+
final File file;
27+
28+
/// The file resource.
29+
final String content;
30+
31+
/// The parsed, unresolved compilation unit for the [content].
32+
final CompilationUnit compilationUnit;
33+
34+
/// The element representing the library containing the compilation [unit].
35+
final LibraryElement libraryElement;
1836

1937
/// The path of the file snippets are being requested for.
2038
final String filePath;
@@ -30,15 +48,33 @@ class DartSnippetRequest {
3048
/// replaced if the snippet is selected.
3149
late final SourceRange replacementRange;
3250

33-
DartSnippetRequest({required this.unit, required this.offset})
34-
: filePath = unit.path {
51+
DartSnippetRequest({required ResolvedUnitResult unit, required this.offset})
52+
: analysisSession = unit.session,
53+
typeProvider = unit.typeProvider,
54+
file = unit.file,
55+
content = unit.content,
56+
compilationUnit = unit.unit,
57+
libraryElement = unit.libraryElement,
58+
filePath = unit.path {
3559
var target = CompletionTarget.forOffset(unit.unit, offset);
3660
context = _getContext(target);
3761
replacementRange = target.computeReplacementRange(offset);
3862
}
3963

40-
/// The analysis session that produced the elements of the request.
41-
AnalysisSession get analysisSession => unit.session;
64+
DartSnippetRequest.fromCompletionResult({
65+
required ResolvedForCompletionResultImpl unit,
66+
required this.offset,
67+
required this.file,
68+
}) : analysisSession = unit.analysisSession,
69+
typeProvider = unit.unitElement.element.typeProvider,
70+
content = unit.content,
71+
compilationUnit = unit.parsedUnit,
72+
libraryElement = unit.unitElement.element,
73+
filePath = unit.path {
74+
var target = CompletionTarget.forOffset(unit.parsedUnit, offset);
75+
context = _getContext(target);
76+
replacementRange = target.computeReplacementRange(offset);
77+
}
4278

4379
/// The resource provider associated with this request.
4480
ResourceProvider get resourceProvider => analysisSession.resourceProvider;

pkg/analysis_server/lib/src/services/snippets/snippet_producer.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import 'package:meta/meta.dart';
2121
abstract class DartSnippetProducer extends SnippetProducer {
2222
final AnalysisSessionHelper sessionHelper;
2323
final CorrectionUtils utils;
24-
final LibraryElement libraryElement;
2524
final bool useSuperParams;
2625

2726
/// A cache of mappings from Elements to their public Library Elements.
@@ -34,18 +33,20 @@ abstract class DartSnippetProducer extends SnippetProducer {
3433
super.request, {
3534
required Map<Element, LibraryElement?> elementImportCache,
3635
}) : sessionHelper = AnalysisSessionHelper(request.analysisSession),
37-
utils = CorrectionUtils(request.unit),
38-
libraryElement = request.unit.libraryElement,
39-
useSuperParams = request.unit.libraryElement.featureSet.isEnabled(
36+
utils = CorrectionUtils.fromUnitAndContent(
37+
request.compilationUnit,
38+
request.content,
39+
),
40+
useSuperParams = request.libraryElement.featureSet.isEnabled(
4041
Feature.super_parameters,
4142
),
4243
_elementImportCache = elementImportCache;
4344

4445
CodeStyleOptions get codeStyleOptions => sessionHelper.session.analysisContext
45-
.getAnalysisOptionsForFile(request.unit.file)
46+
.getAnalysisOptionsForFile(request.file)
4647
.codeStyleOptions;
4748

48-
bool get isInTestDirectory => request.unit.unit.inTestDir;
49+
bool get isInTestDirectory => request.compilationUnit.inTestDir;
4950
}
5051

5152
abstract class FlutterSnippetProducer extends DartSnippetProducer {

pkg/analysis_server_plugin/api.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package:analysis_server_plugin/edit/assist/dart_assist_context.dart:
1616
workspace (getter: ChangeWorkspace)
1717
package:analysis_server_plugin/edit/correction_utils.dart:
1818
CorrectionUtils (class extends Object):
19+
fromUnitAndContent (constructor: CorrectionUtils Function(CompilationUnit, String))
1920
new (constructor: CorrectionUtils Function(ParsedUnitResult))
2021
endOfLine (getter: String)
2122
oneIndent (getter: String)
@@ -200,6 +201,7 @@ package:analyzer/source/source_range.dart:
200201
package:analyzer/src/dart/ast/ast.dart:
201202
AstNode (referenced)
202203
ClassDeclaration (referenced)
204+
CompilationUnit (referenced)
203205
CompilationUnitMember (referenced)
204206
EnumDeclaration (referenced)
205207
Expression (referenced)

pkg/analysis_server_plugin/lib/edit/correction_utils.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ final class CorrectionUtils {
3434
: _unit = result.unit,
3535
_buffer = result.content;
3636

37+
CorrectionUtils.fromUnitAndContent(CompilationUnit unit, String content)
38+
: _unit = unit,
39+
_buffer = content;
40+
3741
/// The EOL sequence to use for this [CompilationUnit].
3842
String get endOfLine {
3943
return _endOfLine ??= _buffer.endOfLine ?? Platform.lineTerminator;

0 commit comments

Comments
 (0)