[analysis_server]/[analyzer_plugin] Make all toJson/fromJson methods go through a central converstion for Path<->URI Strings

This change shouldn't change any current behaviour, but means all "FilePath" types in the legacy protocol spec will go through a (currently no-op) conversion. The server will be able to replace this conversion based on client capabilities in a future CL.

Because a lot of the generated classes are in analyzer_plugin, this also moves the ClientUriConverter class there.

`pkg\analysis_server\test\src\utilities\json_test.dart` contains tests that the toJson/fromJson methods go through the converter recursively (inc. map keys/values/etc.).

Change-Id: If5aec884070128eea594540fd25a9017ada86079
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349060
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2024-01-30 16:58:25 +00:00 committed by Commit Queue
parent 678b8e8746
commit 8d936d38fc
26 changed files with 726 additions and 205 deletions

File diff suppressed because it is too large Load diff

View file

@ -38,7 +38,6 @@ import 'package:analysis_server/src/services/search/search_engine_internal.dart'
import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart';
import 'package:analysis_server/src/services/user_prompts/survey_manager.dart';
import 'package:analysis_server/src/services/user_prompts/user_prompts.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analysis_server/src/utilities/file_string_sink.dart';
import 'package:analysis_server/src/utilities/null_string_sink.dart';
import 'package:analysis_server/src/utilities/process.dart';
@ -76,6 +75,7 @@ import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/extensions/analysis_session.dart';
import 'package:analyzer_plugin/protocol/protocol.dart';
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

View file

@ -12,13 +12,13 @@ import 'package:analysis_server/src/lsp/handlers/handler_reject.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
import 'package:analysis_server/src/lsp/progress.dart';
import 'package:analysis_server/src/request_handler_mixin.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/cancellation.dart';
import 'package:analyzer_plugin/protocol/protocol.dart';
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:language_server_protocol/json_parsing.dart';
import 'package:path/path.dart' as path;

View file

@ -29,7 +29,6 @@ import 'package:analysis_server/src/server/diagnostic_server.dart';
import 'package:analysis_server/src/server/error_notifier.dart';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analysis_server/src/utilities/flutter.dart';
import 'package:analysis_server/src/utilities/process.dart';
import 'package:analyzer/dart/analysis/context_locator.dart';
@ -46,6 +45,7 @@ import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin;
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

View file

@ -18,7 +18,6 @@ import 'package:analysis_server/src/protocol_server.dart' as server
hide AnalysisError;
import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analysis_server/src/services/snippets/snippet.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analysis_server/src/utilities/extensions/string.dart';
import 'package:analyzer/dart/analysis/results.dart' as server;
import 'package:analyzer/error/error.dart' as server;
@ -30,6 +29,7 @@ import 'package:analyzer/src/dart/analysis/search.dart' as server
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:collection/collection.dart';
const languageSourceName = 'dart';

View file

@ -316,7 +316,13 @@ class ServerCapabilitiesComputer {
clientCapabilities: _server.lspClientCapabilities!,
clientConfiguration: _server.lspClientConfiguration,
customDartSchemes: _server.uriConverter.supportedNonFileSchemes,
dartFilters: _server.uriConverter.filters,
dartFilters: [
for (var scheme in {
'file',
..._server.uriConverter.supportedNonFileSchemes
})
TextDocumentFilterWithScheme(language: 'dart', scheme: scheme)
],
pluginTypes: pluginTypes,
);
}

View file

@ -4,8 +4,8 @@
import 'package:analysis_server/src/legacy_analysis_server.dart';
import 'package:analysis_server/src/lsp/test_macros.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:language_server_protocol/protocol_generated.dart';
import 'package:test/expect.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

View file

@ -8,8 +8,8 @@ import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:collection/collection.dart';
import 'package:language_server_protocol/json_parsing.dart';
import 'package:path/path.dart' as path;

View file

@ -13,7 +13,6 @@ import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analysis_server/src/utilities/mocks.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/src/generated/sdk.dart';
@ -23,6 +22,7 @@ import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin;
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:collection/collection.dart';
import 'package:language_server_protocol/json_parsing.dart';
import 'package:path/path.dart' as path;

View file

@ -7,7 +7,7 @@ import 'dart:convert';
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/protocol/protocol_internal.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/utilities/client_uri_converter.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

View file

@ -8,7 +8,6 @@ import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../tool/codebase/failing_tests.dart';
import '../../abstract_context.dart';
void main() {

View file

@ -0,0 +1,230 @@
// Copyright (c) 2024, 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/protocol/protocol_internal.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'
show clientUriConverter;
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(JsonTest);
defineReflectiveTests(JsonWithConvertedFilePathsTest);
});
}
@reflectiveTest
class JsonTest {
final decoder = ResponseDecoder(null);
void test_fromJson() {
var json = {
'offset': 0,
'length': 1,
'label': 'x',
};
var label = ClosingLabel.fromJson(decoder, '', json);
expect(label.label, 'x');
expect(label.offset, 0);
expect(label.length, 1);
}
void test_toJson() {
var closingLabel = ClosingLabel(0, 1, 'x');
var json = closingLabel.toJson();
expect(json, {
'offset': 0,
'length': 1,
'label': 'x',
});
}
}
@reflectiveTest
class JsonWithConvertedFilePathsTest with ResourceProviderMixin {
final decoder = ResponseDecoder(null);
void setUp() {
// These tests use a dummy encoder that just prefixes the FilePaths with
// "Encoded" and "Decoded" to simplify testing. The real implementation will
// convert between file paths and URI strings.
clientUriConverter = _PrefixingUriConverter();
}
void tearDown() {
// Because this is currently global, restore after the tests.
clientUriConverter = ClientUriConverter.noop(pathContext);
}
void test_fromJson_filePath_list() {
var json = {
'files': ['/my/file/1', '/my/file/2']
};
var params = AnalysisFlushResultsParams.fromJson(decoder, '', json);
expect(params.files, ['Decoded /my/file/1', 'Decoded /my/file/2']);
}
void test_fromJson_filePath_map_keyValue() {
var json = {
'included': [],
'excluded': [],
'packageRoots': {
'/my/file/key': '/my/file/value',
}
};
var params = AnalysisSetAnalysisRootsParams.fromJson(decoder, '', json);
expect(params.packageRoots, {
'Decoded /my/file/key': 'Decoded /my/file/value',
});
}
void test_fromJson_filePath_map_value_list() {
var json = {
'subscriptions': {
'CLOSING_LABELS': ['/my/file/value']
}
};
var params = AnalysisSetSubscriptionsParams.fromJson(decoder, '', json);
expect(params.subscriptions, {
AnalysisService.CLOSING_LABELS: ['Decoded /my/file/value'],
});
}
void test_fromJson_filePath_nested() {
var json = {
'fixes': [
{
'error': {
'severity': 'INFO',
'type': 'LINT',
'location': {
'file': '/my/file',
'offset': 0,
'length': 1,
'startLine': 2,
'startColumn': 3
},
'message': 'x',
'code': 'y'
},
'fixes': []
}
]
};
var result = EditGetFixesResult.fromJson(decoder, '', json);
expect(result.fixes.single.error.location.file, 'Decoded /my/file');
}
void test_fromJson_filePath_topLevel() {
var json = {'file': '/my/file', 'labels': []};
var params = AnalysisClosingLabelsParams.fromJson(decoder, '', json);
expect(params.file, 'Decoded /my/file');
}
void test_toJson_list() {
var params = AnalysisFlushResultsParams(['/my/file/1', '/my/file/2']);
var json = params.toJson();
expect(
json,
{
'files': ['Encoded /my/file/1', 'Encoded /my/file/2']
},
);
}
void test_toJson_map_keyValue() {
var params = AnalysisSetAnalysisRootsParams(
[],
[],
packageRoots: {
'/my/file/key': '/my/file/value',
},
);
var json = params.toJson();
expect(json, {
'included': [],
'excluded': [],
'packageRoots': {
'Encoded /my/file/key': 'Encoded /my/file/value',
}
});
}
void test_toJson_map_value_list() {
var params = AnalysisSetSubscriptionsParams(
{
AnalysisService.CLOSING_LABELS: ['/my/file/value'],
},
);
var json = params.toJson();
expect(json, {
'subscriptions': {
'CLOSING_LABELS': ['Encoded /my/file/value']
}
});
}
void test_toJson_nested() {
var result = EditGetFixesResult([
AnalysisErrorFixes(
AnalysisError(
AnalysisErrorSeverity.INFO,
AnalysisErrorType.LINT,
Location('/my/file', 0, 1, 2, 3),
'x',
'y',
),
)
]);
var json = result.toJson();
expect(json, {
'fixes': [
{
'error': {
'severity': 'INFO',
'type': 'LINT',
'location': {
'file': 'Encoded /my/file',
'offset': 0,
'length': 1,
'startLine': 2,
'startColumn': 3
},
'message': 'x',
'code': 'y'
},
'fixes': []
}
]
});
}
void test_toJson_topLevel() {
var closingLabelParams = AnalysisClosingLabelsParams('/my/file', []);
var json = closingLabelParams.toJson();
expect(
json,
{'file': 'Encoded /my/file', 'labels': []},
);
}
}
/// A [ClientUriConverter] that just prefixes the input string for testing.
class _PrefixingUriConverter implements ClientUriConverter {
@override
String fromClientFilePath(String filePathOrUri) => 'Decoded $filePathOrUri';
@override
dynamic noSuchMethod(Invocation invocation) => throw UnimplementedError();
@override
String toClientFilePath(String filePath) => 'Encoded $filePath';
}

View file

@ -4,10 +4,10 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'client_uri_converter_test.dart' as client_uri_converter;
import 'extensions/test_all.dart' as extensions;
import 'flutter_test.dart' as flutter;
import 'import_analyzer_test.dart' as import_analyzer;
import 'json_test.dart' as json;
import 'profiling_test.dart' as profiling;
import 'selection_coverage_test.dart' as selection_coverage;
import 'selection_test.dart' as selection;
@ -16,9 +16,9 @@ import 'strings_test.dart' as strings;
void main() {
defineReflectiveSuite(() {
client_uri_converter.main();
extensions.main();
flutter.main();
json.main();
import_analyzer.main();
profiling.main();
selection_coverage.main();

View file

@ -25,21 +25,31 @@ const Map<String, String> specialElementFlags = {
'deprecated': '0x20'
};
GeneratedFile clientTarget(bool responseRequiresRequestTime) {
GeneratedFile clientTarget(
bool responseRequiresRequestTime, bool requiresProtocolJsonMethods) {
return GeneratedFile(
'../analysis_server_client/lib/src/protocol/protocol_generated.dart',
(String pkgPath) async {
var visitor = CodegenProtocolVisitor('analysis_server_client',
responseRequiresRequestTime, false, readApi(pkgPath));
var visitor = CodegenProtocolVisitor(
'analysis_server_client',
responseRequiresRequestTime,
requiresProtocolJsonMethods,
false,
readApi(pkgPath));
return visitor.collectCode(visitor.visitApi);
});
}
GeneratedFile serverTarget(bool responseRequiresRequestTime) {
GeneratedFile serverTarget(
bool responseRequiresRequestTime, bool requiresProtocolJsonMethods) {
return GeneratedFile('lib/protocol/protocol_generated.dart',
(String pkgPath) async {
var visitor = CodegenProtocolVisitor(path.basename(pkgPath),
responseRequiresRequestTime, true, readApi(pkgPath));
var visitor = CodegenProtocolVisitor(
path.basename(pkgPath),
responseRequiresRequestTime,
requiresProtocolJsonMethods,
true,
readApi(pkgPath));
return visitor.collectCode(visitor.visitApi);
});
}
@ -75,6 +85,10 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
/// parameter.
final bool responseRequiresRequestTime;
/// A flag indicating whether the classes should have `toProtocolJson` and
/// `fromProtocolJson` methods that can handle converting client URIs.
final bool requiresProtocolJsonMethods;
/// A flag indicating whether this generated code is for the server
/// (analysis_server) or for the client (analysis_server_client).
final bool isServer;
@ -88,7 +102,7 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
final Map<String, ImpliedType> impliedTypes;
CodegenProtocolVisitor(this.packageName, this.responseRequiresRequestTime,
this.isServer, Api api)
this.requiresProtocolJsonMethods, this.isServer, Api api)
: toHtmlVisitor = ToHtmlVisitor(api),
impliedTypes = computeImpliedTypes(api),
super(api) {
@ -383,6 +397,8 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
writeln("import 'package:$packageName/protocol/protocol.dart';");
writeln(
"import 'package:$packageName/src/protocol/protocol_internal.dart';");
writeln(
"import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' show clientUriConverter;");
for (var uri in api.types.importUris) {
write("import '");
write(uri);
@ -943,6 +959,12 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
return '$typeName.fromJson(jsonDecoder, $jsonPath, $json)';
}
});
} else if (requiresProtocolJsonMethods &&
referencedDefinition.name == 'FilePath') {
// TODO(dantup): Ensure if the client sends us filepaths instead of
// URIs that we generate good error responses.
return FromJsonSnippet((jsonPath, json) =>
'clientUriConverter.fromClientFilePath(jsonDecoder.decodeString($jsonPath, $json))');
} else {
return fromJsonCode(referencedType);
}
@ -966,7 +988,10 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
}
} else if (type is TypeMap) {
FromJsonCode keyCode;
if (dartType(type.keyType) != 'String') {
var referencedDefinition = api.types[type.keyType.typeName];
if (dartType(type.keyType) != 'String' ||
(requiresProtocolJsonMethods &&
referencedDefinition?.name == 'FilePath')) {
keyCode = fromJsonCode(type.keyType);
} else {
keyCode = FromJsonIdentity();
@ -1060,7 +1085,14 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
/// Compute the code necessary to convert [type] to JSON.
ToJsonCode toJsonCode(TypeDecl type) {
var resolvedType = resolveTypeReferenceChain(type);
if (resolvedType is TypeReference) {
if (type is TypeReference &&
requiresProtocolJsonMethods &&
type.typeName == 'FilePath') {
return ToJsonSnippet(
dartType(type),
(String value) => 'clientUriConverter.toClientFilePath($value)',
);
} else if (resolvedType is TypeReference) {
return ToJsonIdentity(dartType(type));
} else if (resolvedType is TypeList) {
var itemCode = toJsonCode(resolvedType.itemType);
@ -1072,7 +1104,10 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
}
} else if (resolvedType is TypeMap) {
ToJsonCode keyCode;
if (dartType(resolvedType.keyType) != 'String') {
var referencedDefinition = api.types[resolvedType.keyType.typeName];
if (dartType(resolvedType.keyType) != 'String' ||
(requiresProtocolJsonMethods &&
referencedDefinition?.name == 'FilePath')) {
keyCode = toJsonCode(resolvedType.keyType);
} else {
keyCode = ToJsonIdentity(dartType(resolvedType.keyType));

View file

@ -29,8 +29,8 @@ List<GeneratedContent> get allTargets {
var targets = <GeneratedContent>[];
targets.add(codegen_analysis_server.target);
targets.add(codegen_dart_notification_handler.clientTarget());
targets.add(codegen_dart_protocol.clientTarget(false));
targets.add(codegen_dart_protocol.serverTarget(false));
targets.add(codegen_dart_protocol.clientTarget(false, false));
targets.add(codegen_dart_protocol.serverTarget(false, true));
targets.add(codegen_java_types.targetDir);
targets.add(codegen_inttest_methods.target);
targets.add(codegen_matchers.target);

View file

@ -9,6 +9,8 @@ import 'package:analysis_server_client/src/protocol/protocol_base.dart';
import 'package:analysis_server_client/src/protocol/protocol_common.dart';
import 'package:analysis_server_client/src/protocol/protocol_generated.dart';
final clientUriConverter = _ClientUriConverter();
final Map<String, RefactoringKind> REQUEST_ID_REFACTORING_KINDS =
HashMap<String, RefactoringKind>();
@ -462,3 +464,14 @@ abstract class ResponseResult implements HasToJson {
/// given [id].
Response toResponse(String id);
}
/// A dummy converter with the required API for the copy of protocol classes
/// in analysis_server_client.
class _ClientUriConverter {
// TODO(dantup): Determine whether analysis_server_client needs to support
// macro mappings.
String fromClientFilePath(String filePath) => filePath;
String toClientFilePath(String filePath) => filePath;
}

View file

@ -24,6 +24,8 @@ mixin ResourceProviderMixin {
? MemoryResourceProvider(context: path.windows)
: MemoryResourceProvider();
path.Context get pathContext => resourceProvider.pathContext;
String convertPath(String path) => resourceProvider.convertPath(path);
void deleteAnalysisOptionsYamlFile(String directoryPath) {

View file

@ -2798,7 +2798,8 @@ class Location implements HasToJson {
if (json is Map) {
String file;
if (json.containsKey('file')) {
file = jsonDecoder.decodeString('$jsonPath.file', json['file']);
file = clientUriConverter.fromClientFilePath(
jsonDecoder.decodeString('$jsonPath.file', json['file']));
} else {
throw jsonDecoder.mismatch(jsonPath, 'file');
}
@ -2847,7 +2848,7 @@ class Location implements HasToJson {
@override
Map<String, Object> toJson() {
var result = <String, Object>{};
result['file'] = file;
result['file'] = clientUriConverter.toClientFilePath(file);
result['offset'] = offset;
result['length'] = length;
result['startLine'] = startLine;
@ -3538,7 +3539,8 @@ class Position implements HasToJson {
if (json is Map) {
String file;
if (json.containsKey('file')) {
file = jsonDecoder.decodeString('$jsonPath.file', json['file']);
file = clientUriConverter.fromClientFilePath(
jsonDecoder.decodeString('$jsonPath.file', json['file']));
} else {
throw jsonDecoder.mismatch(jsonPath, 'file');
}
@ -3557,7 +3559,7 @@ class Position implements HasToJson {
@override
Map<String, Object> toJson() {
var result = <String, Object>{};
result['file'] = file;
result['file'] = clientUriConverter.toClientFilePath(file);
result['offset'] = offset;
return result;
}
@ -4390,7 +4392,8 @@ class SourceFileEdit implements HasToJson {
if (json is Map) {
String file;
if (json.containsKey('file')) {
file = jsonDecoder.decodeString('$jsonPath.file', json['file']);
file = clientUriConverter.fromClientFilePath(
jsonDecoder.decodeString('$jsonPath.file', json['file']));
} else {
throw jsonDecoder.mismatch(jsonPath, 'file');
}
@ -4420,7 +4423,7 @@ class SourceFileEdit implements HasToJson {
@override
Map<String, Object> toJson() {
var result = <String, Object>{};
result['file'] = file;
result['file'] = clientUriConverter.toClientFilePath(file);
result['fileStamp'] = fileStamp;
result['edits'] = edits.map((SourceEdit value) => value.toJson()).toList();
return result;

View file

@ -8,7 +8,16 @@ import 'dart:convert' hide JsonDecoder;
import 'package:analyzer_plugin/protocol/protocol.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:analyzer_plugin/utilities/change_builder/conflicting_edit_exception.dart';
import 'package:path/path.dart' as path;
/// This can be set by server (or tests) to change into URI mode for all legacy
/// protocol JSON.
///
// TODO(dantup): Consider replacing this global with encoders being passed to
// toJson/fromJson methods.
var clientUriConverter = ClientUriConverter.noop(path.context);
final Map<String, RefactoringKind> REQUEST_ID_REFACTORING_KINDS =
HashMap<String, RefactoringKind>();

View file

@ -2,7 +2,6 @@
// 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/lsp_protocol/protocol.dart';
import 'package:path/path.dart' as path;
/// The file suffix used for virtual macro files in the analyzer.
@ -17,7 +16,7 @@ const macroClientUriScheme = 'dart-macro+file';
/// The simplest form of this class simple translates between file paths and
/// `file://` URIs but depending on client capabilities some paths/URIs may be
/// re-written to support features like virtual files for macros.
class ClientUriConverter {
abstract class ClientUriConverter {
final path.Context _context;
/// The URI schemes that are supported by this converter.
@ -29,27 +28,69 @@ class ClientUriConverter {
/// The URI schemes that are supported by this converter except 'file'.
final Set<String> supportedNonFileSchemes;
/// A set of document filters for Dart files in the supported schemes.
final List<TextDocumentFilterWithScheme> filters;
/// Creates a converter that does nothing besides translation between file
/// paths and `file://` URIs.
ClientUriConverter.noop(path.Context context) : this._(context);
factory ClientUriConverter.noop(path.Context context) =>
_NoOpConverter(context);
/// Creates a converter that translates paths/URIs for virtual files such as
/// those created by macros.
ClientUriConverter.withVirtualFileSupport(path.Context context)
: this._(context, {macroClientUriScheme});
factory ClientUriConverter.withVirtualFileSupport(path.Context context) =>
_VirtualFileClientUriConverter(context);
ClientUriConverter._(this._context, [this.supportedNonFileSchemes = const {}])
: supportedSchemes = {'file', ...supportedNonFileSchemes},
filters = [
for (var scheme in {'file', ...supportedNonFileSchemes})
TextDocumentFilterWithScheme(language: 'dart', scheme: scheme)
];
: supportedSchemes = {'file', ...supportedNonFileSchemes};
/// Converts client FilePath (which may be a URI or a file path depending on
/// client capbilities) into a file path/reference from the analyzer.
///
/// This is the legacy protocol equiv of [fromClientUri].
String fromClientFilePath(String filePathOrUri);
/// Converts a URI provided by the client into a file path/reference that can
/// be used by the analyzer.
///
/// This is the LSP equiv of [fromClientFilePath].
String fromClientUri(Uri uri);
/// Converts a file path/reference from the analyzer into a client FilePath
/// (which may be a URI or a file path depending on client capbilities).
///
/// This is the legacy protocol equiv of [toClientUri].
String toClientFilePath(String filePath);
/// Converts a file path/reference from the analyzer into a URI to be sent to
/// the client.
///
/// This is the LSP equiv of [toClientFilePath].
Uri toClientUri(String filePath);
}
class _NoOpConverter extends ClientUriConverter {
_NoOpConverter(super.context) : super._();
@override
String fromClientFilePath(String filePathOrUri) => filePathOrUri;
@override
String fromClientUri(Uri uri) => _context.fromUri(uri);
@override
String toClientFilePath(String filePath) => filePath;
@override
Uri toClientUri(String filePath) => _context.toUri(filePath);
}
class _VirtualFileClientUriConverter extends ClientUriConverter {
_VirtualFileClientUriConverter(path.Context context)
: super._(context, {macroClientUriScheme});
@override
String fromClientFilePath(String filePathOrUri) =>
fromClientUri(Uri.parse(filePathOrUri));
@override
String fromClientUri(Uri uri) {
// For URIs with no scheme, assume it was a relative path and provide a
// better message than "scheme '' is not supported".
@ -81,8 +122,10 @@ class ClientUriConverter {
}
}
/// Converts a file path/reference from the analyzer into a URI to be sent to
/// the client.
@override
String toClientFilePath(String filePath) => toClientUri(filePath).toString();
@override
Uri toClientUri(String filePath) {
// Map '/.../x.macro.dart' onto macro scheme.
if (filePath.endsWith(macroClientFileSuffix) &&

View file

@ -12,6 +12,7 @@ dependencies:
dart_style: ^2.2.1
pub_semver: ^2.1.0
yaml: ^3.1.0
path: ^1.9.0
# We use 'any' version constraints here as we get our package versions from
# the dart-lang/sdk repo's DEPS file. Note that this is a special case; the
@ -22,6 +23,5 @@ dev_dependencies:
lints: any
linter: any
meta: any
path: any
test_reflective_loader: any
test: any

View file

@ -2,13 +2,11 @@
// 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/utilities/client_uri_converter.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../tool/codebase/failing_tests.dart';
import '../../abstract_single_unit.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ClientUriConverterTest);
@ -16,7 +14,7 @@ void main() {
}
@reflectiveTest
class ClientUriConverterTest extends AbstractSingleUnitTest {
class ClientUriConverterTest with ResourceProviderMixin {
Future<void> test_noop_fromUri() async {
var converter = ClientUriConverter.noop(pathContext);

View file

@ -5,6 +5,7 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'change_builder/test_all.dart' as change_builder;
import 'client_uri_converter_test.dart' as client_uri_converter;
import 'completion/test_all.dart' as completion;
import 'navigation/test_all.dart' as navigation;
import 'string_utilities_test.dart' as string_utilities;
@ -13,6 +14,7 @@ import 'visitors/test_all.dart' as visitors;
void main() {
defineReflectiveSuite(() {
change_builder.main();
client_uri_converter.main();
completion.main();
navigation.main();
string_utilities.main();

View file

@ -25,11 +25,15 @@ const Map<String, String> specialElementFlags = {
'deprecated': '0x20'
};
GeneratedFile target(bool responseRequiresRequestTime) {
GeneratedFile target(
bool responseRequiresRequestTime, bool requiresProtocolJsonMethods) {
return GeneratedFile('lib/protocol/protocol_generated.dart',
(String pkgPath) async {
var visitor = CodegenProtocolVisitor(
path.basename(pkgPath), responseRequiresRequestTime, readApi(pkgPath));
path.basename(pkgPath),
responseRequiresRequestTime,
requiresProtocolJsonMethods,
readApi(pkgPath));
return visitor.collectCode(visitor.visitApi);
});
}
@ -65,6 +69,10 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
/// parameter.
final bool responseRequiresRequestTime;
/// A flag indicating whether the classes should have `toProtocolJson` and
/// `fromProtocolJson` methods that can handle converting client URIs.
final bool requiresProtocolJsonMethods;
/// Visitor used to produce doc comments.
final ToHtmlVisitor toHtmlVisitor;
@ -73,8 +81,8 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
/// notifications, etc.
final Map<String, ImpliedType> impliedTypes;
CodegenProtocolVisitor(
this.packageName, this.responseRequiresRequestTime, Api api)
CodegenProtocolVisitor(this.packageName, this.responseRequiresRequestTime,
this.requiresProtocolJsonMethods, Api api)
: toHtmlVisitor = ToHtmlVisitor(api),
impliedTypes = computeImpliedTypes(api),
super(api) {
@ -957,6 +965,10 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
return '$typeName.fromJson(jsonDecoder, $jsonPath, $json)';
}
});
} else if (requiresProtocolJsonMethods &&
referencedDefinition.name == 'FilePath') {
return FromJsonSnippet((jsonPath, json) =>
'clientUriConverter.fromClientFilePath(jsonDecoder.decodeString($jsonPath, $json))');
} else {
return fromJsonCode(referencedType);
}
@ -977,7 +989,10 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
}
} else if (type is TypeMap) {
FromJsonCode keyCode;
if (dartType(type.keyType) != 'String') {
var referencedDefinition = api.types[type.keyType.typeName];
if (dartType(type.keyType) != 'String' ||
(requiresProtocolJsonMethods &&
referencedDefinition?.name == 'FilePath')) {
keyCode = fromJsonCode(type.keyType);
} else {
keyCode = FromJsonIdentity();
@ -1071,7 +1086,14 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
/// Compute the code necessary to convert [type] to JSON.
ToJsonCode toJsonCode(TypeDecl type) {
var resolvedType = resolveTypeReferenceChain(type);
if (resolvedType is TypeReference) {
if (type is TypeReference &&
requiresProtocolJsonMethods &&
type.typeName == 'FilePath') {
return ToJsonSnippet(
dartType(type),
(String value) => 'clientUriConverter.toClientFilePath($value)',
);
} else if (resolvedType is TypeReference) {
return ToJsonIdentity(dartType(type));
} else if (resolvedType is TypeList) {
var itemCode = toJsonCode(resolvedType.itemType);
@ -1083,7 +1105,10 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
}
} else if (resolvedType is TypeMap) {
ToJsonCode keyCode;
if (dartType(resolvedType.keyType) != 'String') {
var referencedDefinition = api.types[resolvedType.keyType.typeName];
if (dartType(resolvedType.keyType) != 'String' ||
(requiresProtocolJsonMethods &&
referencedDefinition?.name == 'FilePath')) {
keyCode = toJsonCode(resolvedType.keyType);
} else {
keyCode = ToJsonIdentity(dartType(resolvedType.keyType));

View file

@ -10,19 +10,28 @@ import 'codegen_dart_protocol.dart';
import 'from_html.dart';
import 'implied_types.dart';
GeneratedFile clientTarget(bool responseRequiresRequestTime) => GeneratedFile(
GeneratedFile clientTarget(
bool responseRequiresRequestTime, bool requiresProtocolJsonMethods) =>
GeneratedFile(
'../analysis_server_client/lib/src/protocol/protocol_common.dart',
(String pkgPath) async {
var visitor = CodegenCommonVisitor(
path.basename(pkgPath), responseRequiresRequestTime, readApi(pkgPath),
path.basename(pkgPath),
responseRequiresRequestTime,
requiresProtocolJsonMethods,
readApi(pkgPath),
forClient: true);
return visitor.collectCode(visitor.visitApi);
});
GeneratedFile pluginTarget(bool responseRequiresRequestTime) =>
GeneratedFile pluginTarget(
bool responseRequiresRequestTime, bool requiresProtocolJsonMethods) =>
GeneratedFile('lib/protocol/protocol_common.dart', (String pkgPath) async {
var visitor = CodegenCommonVisitor(path.basename(pkgPath),
responseRequiresRequestTime, readApi(pkgPath));
var visitor = CodegenCommonVisitor(
path.basename(pkgPath),
responseRequiresRequestTime,
requiresProtocolJsonMethods,
readApi(pkgPath));
return visitor.collectCode(visitor.visitApi);
});
@ -34,8 +43,8 @@ class CodegenCommonVisitor extends CodegenProtocolVisitor {
/// Initialize a newly created visitor to generate code in the package with
/// the given [packageName] corresponding to the types in the given [api] that
/// are common to multiple protocols.
CodegenCommonVisitor(
super.packageName, super.responseRequiresRequestTime, super.api,
CodegenCommonVisitor(super.packageName, super.responseRequiresRequestTime,
super.requiresProtocolJsonMethods, super.api,
{this.forClient = false});
@override

View file

@ -24,11 +24,11 @@ Future<void> main() async {
/// Get a list of all generated targets.
List<GeneratedContent> get allTargets {
var targets = <GeneratedContent>[];
targets.add(codegen_dart_protocol.target(true));
targets.add(codegen_dart_protocol.target(true, false));
targets.add(codegen_inttest_methods.target);
targets.add(codegen_matchers.target);
targets.add(codegen_protocol_common.pluginTarget(true));
targets.add(codegen_protocol_common.clientTarget(true));
targets.add(codegen_protocol_common.pluginTarget(true, true));
targets.add(codegen_protocol_common.clientTarget(true, false));
targets.add(codegen_protocol_constants.target);
targets.add(to_html.target);
return targets;