Skip to content

Commit 82a01bb

Browse files
bwilkersonCommit Queue
authored andcommitted
Add a custom LSP request to produce a summary of a file
The exact format of the summary is yet to be decided, but at the moment it's producing an abbreviated form of Dart that doesn't include function bodies or documentation comments. No attempt is made to remove in-line comments, but we might want to consider doing that. Change-Id: Iec6e27c54e3099a596d50d5c461356006694690b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/440682 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Jake Macdonald <[email protected]>
1 parent aad9e0a commit 82a01bb

File tree

9 files changed

+634
-0
lines changed

9 files changed

+634
-0
lines changed

pkg/analysis_server/lib/src/lsp/constants.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ abstract final class CustomMethods {
149149
static const publishFlutterOutline = Method(
150150
'dart/textDocument/publishFlutterOutline',
151151
);
152+
static const summary = Method('dart/textDocument/summary');
152153
static const super_ = Method('dart/textDocument/super');
153154
static const imports = Method('dart/textDocument/imports');
154155
static const dartTextDocumentContent = Method('dart/textDocumentContent');
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element;
6+
import 'package:analysis_server/src/lsp/constants.dart';
7+
import 'package:analysis_server/src/lsp/error_or.dart';
8+
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
9+
import 'package:analysis_server/src/lsp/mapping.dart';
10+
import 'package:analyzer/dart/analysis/results.dart';
11+
import 'package:analyzer/dart/ast/ast.dart';
12+
13+
class SummaryHandler
14+
extends
15+
SharedMessageHandler<DartTextDocumentSummaryParams, DocumentSummary> {
16+
SummaryHandler(super.server);
17+
18+
@override
19+
Method get handlesMessage => CustomMethods.summary;
20+
21+
@override
22+
LspJsonHandler<DartTextDocumentSummaryParams> get jsonHandler =>
23+
DartTextDocumentSummaryParams.jsonHandler;
24+
25+
@override
26+
bool get requiresTrustedCaller => false;
27+
28+
@override
29+
Future<ErrorOr<DocumentSummary>> handle(
30+
DartTextDocumentSummaryParams params,
31+
MessageInfo message,
32+
CancellationToken token,
33+
) async {
34+
if (!isDartUri(params.uri)) {
35+
return success(DocumentSummary());
36+
}
37+
38+
var path = pathOfUri(params.uri);
39+
var resultOr = await path.mapResult(requireUnresolvedUnit);
40+
if (resultOr.isError) {
41+
return ErrorOr.error(resultOr.errorOrNull!);
42+
}
43+
return resultOr.mapResultSync((result) {
44+
try {
45+
var summary = SummaryWriter(result).summarize();
46+
return success(DocumentSummary(summary: summary));
47+
} catch (e) {
48+
return error(ErrorCodes.InternalError, 'Failed to create a summary.');
49+
}
50+
});
51+
}
52+
}
53+
54+
/// An objct used to write a summary for a single file.
55+
///
56+
/// The summary contains the public API for the library, but doesn't include
57+
/// - private members
58+
/// - bodies of functions / methods
59+
/// - initializers of variables / fields
60+
class SummaryWriter {
61+
final ParsedUnitResult result;
62+
63+
final StringBuffer buffer = StringBuffer();
64+
65+
SummaryWriter(this.result);
66+
67+
void copyRange(int offset, int end) {
68+
buffer.write(result.content.substring(offset, end));
69+
}
70+
71+
String summarize() {
72+
for (var declaration in result.unit.declarations) {
73+
switch (declaration) {
74+
case ClassDeclaration():
75+
summarizeClass(declaration);
76+
case EnumDeclaration():
77+
summarizeEnum(declaration);
78+
case ExtensionDeclaration():
79+
summarizeExtension(declaration);
80+
case ExtensionTypeDeclaration():
81+
summarizeExtensionType(declaration);
82+
case FunctionDeclaration():
83+
summarizeFunction(declaration);
84+
case MixinDeclaration():
85+
summarizeMixin(declaration);
86+
case TopLevelVariableDeclaration():
87+
summarizeVariable(declaration);
88+
case TypeAlias():
89+
summarizeTypeAlias(declaration);
90+
}
91+
}
92+
return buffer.toString();
93+
}
94+
95+
void summarizeClass(ClassDeclaration declaration) {
96+
var name = declaration.name.lexeme;
97+
if (name.isEmpty || Identifier.isPrivateName(name)) {
98+
return;
99+
}
100+
101+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
102+
var end = declaration.leftBracket.end;
103+
copyRange(offset, end);
104+
buffer.writeln();
105+
summarizeMembers(declaration.members);
106+
buffer.writeln('}');
107+
}
108+
109+
void summarizeConstructor(ConstructorDeclaration declaration) {
110+
var name = declaration.name?.lexeme;
111+
if (name != null && (name.isEmpty || Identifier.isPrivateName(name))) {
112+
return;
113+
}
114+
115+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
116+
var end = declaration.parameters.rightParenthesis.end;
117+
copyRange(offset, end);
118+
buffer.writeln(';');
119+
}
120+
121+
void summarizeEnum(EnumDeclaration declaration) {
122+
var name = declaration.name.lexeme;
123+
if (name.isEmpty || Identifier.isPrivateName(name)) {
124+
return;
125+
}
126+
127+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
128+
var end = declaration.leftBracket.end;
129+
copyRange(offset, end);
130+
buffer.writeln();
131+
summarizeEnumConstants(declaration.constants);
132+
summarizeMembers(declaration.members);
133+
buffer.writeln('}');
134+
}
135+
136+
void summarizeEnumConstants(NodeList<EnumConstantDeclaration> constants) {
137+
buffer.write(' ');
138+
var separator = '';
139+
for (var constant in constants) {
140+
buffer.write(separator);
141+
var offset = constant.firstTokenAfterCommentAndMetadata.offset;
142+
var end = constant.endToken.end;
143+
copyRange(offset, end);
144+
separator = ', ';
145+
}
146+
buffer.writeln(';');
147+
}
148+
149+
void summarizeExtension(ExtensionDeclaration declaration) {
150+
var name = declaration.name?.lexeme;
151+
if (name != null && Identifier.isPrivateName(name)) {
152+
return;
153+
}
154+
155+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
156+
var end = declaration.leftBracket.end;
157+
copyRange(offset, end);
158+
buffer.writeln();
159+
summarizeMembers(declaration.members);
160+
buffer.writeln('}');
161+
}
162+
163+
void summarizeExtensionType(ExtensionTypeDeclaration declaration) {
164+
var name = declaration.name.lexeme;
165+
if (name.isEmpty || Identifier.isPrivateName(name)) {
166+
return;
167+
}
168+
169+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
170+
var end = declaration.leftBracket.end;
171+
copyRange(offset, end);
172+
buffer.writeln();
173+
summarizeMembers(declaration.members);
174+
buffer.writeln('}');
175+
}
176+
177+
void summarizeFields(FieldDeclaration declaration) {
178+
var fields = declaration.fields;
179+
var variableList = fields.variables;
180+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
181+
var end = variableList.beginToken!.previous!.end;
182+
copyRange(offset, end);
183+
var separator = ' ';
184+
for (var field in variableList) {
185+
buffer.write(separator);
186+
buffer.write(field.name.lexeme);
187+
separator = ', ';
188+
}
189+
buffer.writeln(';');
190+
}
191+
192+
void summarizeFunction(FunctionDeclaration declaration) {
193+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
194+
var end = declaration.functionExpression.body.beginToken.previous!.end;
195+
copyRange(offset, end);
196+
buffer.writeln(';');
197+
}
198+
199+
void summarizeMembers(NodeList<ClassMember> members) {
200+
for (var member in members) {
201+
buffer.write(' ');
202+
switch (member) {
203+
case ConstructorDeclaration():
204+
summarizeConstructor(member);
205+
case FieldDeclaration():
206+
summarizeFields(member);
207+
case MethodDeclaration():
208+
summarizeMethod(member);
209+
}
210+
}
211+
}
212+
213+
void summarizeMethod(MethodDeclaration declaration) {
214+
var name = declaration.name.lexeme;
215+
if (name.isEmpty || Identifier.isPrivateName(name)) {
216+
return;
217+
}
218+
219+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
220+
var end = declaration.body.beginToken.previous!.end;
221+
copyRange(offset, end);
222+
buffer.writeln(';');
223+
}
224+
225+
void summarizeMixin(MixinDeclaration declaration) {
226+
var name = declaration.name.lexeme;
227+
if (name.isEmpty || Identifier.isPrivateName(name)) {
228+
return;
229+
}
230+
231+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
232+
var end = declaration.leftBracket.end;
233+
copyRange(offset, end);
234+
buffer.writeln();
235+
summarizeMembers(declaration.members);
236+
buffer.writeln('}');
237+
}
238+
239+
void summarizeTypeAlias(TypeAlias declaration) {
240+
var name = declaration.name.lexeme;
241+
if (name.isEmpty || Identifier.isPrivateName(name)) {
242+
return;
243+
}
244+
245+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
246+
var end = declaration.endToken.end;
247+
copyRange(offset, end);
248+
buffer.writeln();
249+
}
250+
251+
void summarizeVariable(TopLevelVariableDeclaration declaration) {
252+
var variables = declaration.variables;
253+
var variableList = variables.variables;
254+
var offset = declaration.firstTokenAfterCommentAndMetadata.offset;
255+
var end = variableList.beginToken!.previous!.end;
256+
copyRange(offset, end);
257+
var separator = ' ';
258+
for (var variable in variableList) {
259+
buffer.write(separator);
260+
buffer.write(variable.name.lexeme);
261+
separator = ', ';
262+
}
263+
buffer.writeln(';');
264+
}
265+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:analysis_server/src/lsp/handlers/custom/handler_diagnostic_serve
1818
import 'package:analysis_server/src/lsp/handlers/custom/handler_experimental_echo.dart';
1919
import 'package:analysis_server/src/lsp/handlers/custom/handler_imports.dart';
2020
import 'package:analysis_server/src/lsp/handlers/custom/handler_reanalyze.dart';
21+
import 'package:analysis_server/src/lsp/handlers/custom/handler_summary.dart';
2122
import 'package:analysis_server/src/lsp/handlers/custom/handler_super.dart';
2223
import 'package:analysis_server/src/lsp/handlers/custom/handler_update_diagnostic_information.dart';
2324
import 'package:analysis_server/src/lsp/handlers/handler_call_hierarchy.dart';
@@ -146,6 +147,7 @@ class InitializedStateMessageHandler extends ServerStateMessageHandler {
146147
PrepareCallHierarchyHandler.new,
147148
PrepareTypeHierarchyHandler.new,
148149
SignatureHelpHandler.new,
150+
SummaryHandler.new,
149151
SuperHandler.new,
150152
TypeDefinitionHandler.new,
151153
TypeHierarchySubtypesHandler.new,

pkg/analysis_server/test/lsp/request_helpers_mixin.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,14 @@ mixin LspRequestHelpersMixin {
783783
return expectSuccessfulResponseTo(request, SignatureHelp.fromJson);
784784
}
785785

786+
Future<DocumentSummary> getSummary(Uri uri) {
787+
var request = makeRequest(
788+
CustomMethods.summary,
789+
DartTextDocumentSummaryParams(uri: uri),
790+
);
791+
return expectSuccessfulResponseTo(request, DocumentSummary.fromJson);
792+
}
793+
786794
Future<Location> getSuper(Uri uri, Position pos) {
787795
var request = makeRequest(
788796
CustomMethods.super_,

0 commit comments

Comments
 (0)