[analysis_server] Use public (non-src) URIs for Flutter snippet imports
Fixes https://github.com/dart-lang/sdk/issues/49081. Change-Id: I0734b4f45c72d70f7b32640bed6b6ec2e8130c01 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273841 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
committed by
Commit Queue
parent
badf1c5a0e
commit
c24f36d470
@@ -5,7 +5,6 @@
|
||||
import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
|
||||
import 'package:analysis_server/src/services/correction/change_workspace.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix_internal.dart';
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
@@ -15,6 +14,7 @@ import 'package:analyzer/source/line_info.dart';
|
||||
import 'package:analyzer/src/dart/analysis/file_state.dart';
|
||||
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
|
||||
import 'package:analyzer/src/dart/micro/resolve_file.dart';
|
||||
import 'package:analyzer/src/services/top_level_declarations.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
|
||||
|
||||
class CiderErrorFixes {
|
||||
|
||||
@@ -519,6 +519,9 @@ class CompletionHandler extends MessageHandler<CompletionParams, CompletionList>
|
||||
try {
|
||||
unrankedResults =
|
||||
await performance.runAsync('getSnippets', (performance) async {
|
||||
// TODO(dantup): Pass `fuzzy` into here so we can filter snippets
|
||||
// before computing them to avoid looking up Element->Public Library
|
||||
// if they won't be included.
|
||||
final snippets = await _getDartSnippetItems(
|
||||
clientCapabilities: capabilities,
|
||||
unit: unit,
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
import 'package:analysis_server/plugin/edit/fix/fix_dart.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix/dart/extensions.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix_internal.dart';
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/error/error.dart';
|
||||
import 'package:analyzer/instrumentation/service.dart';
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer/src/services/top_level_declarations.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
|
||||
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ class FlutterStatefulWidget extends FlutterSnippetProducer
|
||||
final classStatefulWidget = this.classStatefulWidget!;
|
||||
final classState = this.classState!;
|
||||
|
||||
await builder.addDartFileEdit(request.filePath, (builder) {
|
||||
await builder.addDartFileEdit(request.filePath, (builder) async {
|
||||
await addImports(builder);
|
||||
|
||||
builder.addReplacement(request.replacementRange, (builder) {
|
||||
// Write the StatefulWidget class
|
||||
builder.writeClassDeclaration(
|
||||
|
||||
+3
-1
@@ -37,7 +37,9 @@ class FlutterStatefulWidgetWithAnimationController
|
||||
final classSingleTickerProviderStateMixin =
|
||||
this.classSingleTickerProviderStateMixin!;
|
||||
|
||||
await builder.addDartFileEdit(request.filePath, (builder) {
|
||||
await builder.addDartFileEdit(request.filePath, (builder) async {
|
||||
await addImports(builder);
|
||||
|
||||
builder.addReplacement(request.replacementRange, (builder) {
|
||||
// Write the StatefulWidget class
|
||||
builder.writeClassDeclaration(
|
||||
|
||||
@@ -28,7 +28,9 @@ class FlutterStatelessWidget extends FlutterSnippetProducer
|
||||
// Checked by isValid().
|
||||
final classStatelessWidget = this.classStatelessWidget!;
|
||||
|
||||
await builder.addDartFileEdit(request.filePath, (builder) {
|
||||
await builder.addDartFileEdit(request.filePath, (builder) async {
|
||||
await addImports(builder);
|
||||
|
||||
builder.addReplacement(request.replacementRange, (builder) {
|
||||
builder.writeClassDeclaration(
|
||||
widgetClassName,
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/analysis/session_helper.dart';
|
||||
import 'package:analyzer/src/lint/linter.dart';
|
||||
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart'
|
||||
show DartFileEditBuilderImpl;
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@@ -50,13 +52,38 @@ abstract class FlutterSnippetProducer extends DartSnippetProducer {
|
||||
late ClassElement? classWidget;
|
||||
late ClassElement? classPlaceholder;
|
||||
|
||||
/// Elements that need to be imported for generated code to be valid.
|
||||
///
|
||||
/// Calling [getClass] or [getMixin] records elements in this set.
|
||||
/// Calling [addImports] will add any required imports to the supplied
|
||||
/// builder.
|
||||
final Set<Element> _requiredElementImports = {};
|
||||
|
||||
FlutterSnippetProducer(super.request);
|
||||
|
||||
Future<ClassElement?> getClass(String name) =>
|
||||
sessionHelper.getClass(flutter.widgetsUri, name);
|
||||
/// Adds public imports for any elements fetched by [getClass] and [getMixin]
|
||||
/// to [builder].
|
||||
Future<void> addImports(DartFileEditBuilder builder) async {
|
||||
final dartBuilder = builder as DartFileEditBuilderImpl;
|
||||
await Future.wait(
|
||||
_requiredElementImports.map(dartBuilder.importElementLibrary));
|
||||
}
|
||||
|
||||
Future<MixinElement?> getMixin(String name) =>
|
||||
sessionHelper.getMixin(flutter.widgetsUri, name);
|
||||
Future<ClassElement?> getClass(String name) async {
|
||||
final class_ = await sessionHelper.getClass(flutter.widgetsUri, name);
|
||||
if (class_ != null) {
|
||||
_requiredElementImports.add(class_);
|
||||
}
|
||||
return class_;
|
||||
}
|
||||
|
||||
Future<MixinElement?> getMixin(String name) async {
|
||||
final mixin = await sessionHelper.getMixin(flutter.widgetsUri, name);
|
||||
if (mixin != null) {
|
||||
_requiredElementImports.add(mixin);
|
||||
}
|
||||
return mixin;
|
||||
}
|
||||
|
||||
DartType getType(
|
||||
InterfaceElement classElement, [
|
||||
|
||||
@@ -3731,8 +3731,7 @@ void f() {
|
||||
class FlutterSnippetCompletionTest extends SnippetCompletionTest {
|
||||
/// Standard import statements expected for basic Widgets.
|
||||
String get expectedImports => '''
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';''';
|
||||
import 'package:flutter/widgets.dart';''';
|
||||
|
||||
/// Nullability suffix expected in this test class.
|
||||
///
|
||||
@@ -4007,9 +4006,7 @@ class FlutterSnippetCompletionWithoutNullSafetyTest
|
||||
extends FlutterSnippetCompletionTest {
|
||||
@override
|
||||
String get expectedImports => '''
|
||||
import 'package:flutter/src/foundation/key.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';''';
|
||||
import 'package:flutter/widgets.dart';''';
|
||||
|
||||
@override
|
||||
String get expectedNullableSuffix => '';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:analysis_server/src/protocol_server.dart';
|
||||
import 'package:analysis_server/src/services/snippets/dart/flutter_stateful_widget.dart';
|
||||
import 'package:analyzer/src/test_utilities/test_code_format.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
@@ -38,9 +39,7 @@ class FlutterStatefulWidgetTest extends FlutterSnippetProducerTest {
|
||||
code = SourceEdit.applySequence(code, edit.edits);
|
||||
}
|
||||
expect(code, '''
|
||||
import 'package:flutter/src/foundation/key.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MyWidget extends StatefulWidget {
|
||||
const MyWidget({Key? key}) : super(key: key);
|
||||
@@ -69,44 +68,22 @@ class _MyWidgetState extends State<MyWidget> {
|
||||
final snippet = await expectValidSnippet('^');
|
||||
expect(snippet.prefix, prefix);
|
||||
expect(snippet.label, label);
|
||||
var code = '';
|
||||
expect(snippet.change.edits, hasLength(1));
|
||||
for (var edit in snippet.change.edits) {
|
||||
code = SourceEdit.applySequence(code, edit.edits);
|
||||
}
|
||||
expect(code, '''
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
final expected = TestCode.parse('''
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MyWidget extends StatefulWidget {
|
||||
const MyWidget({super.key});
|
||||
class /*0*/MyWidget extends StatefulWidget {
|
||||
const /*1*/MyWidget({super.key});
|
||||
|
||||
@override
|
||||
State<MyWidget> createState() => _MyWidgetState();
|
||||
State</*2*/MyWidget> createState() => _/*3*/MyWidgetState();
|
||||
}
|
||||
|
||||
class _MyWidgetState extends State<MyWidget> {
|
||||
class _/*4*/MyWidgetState extends State</*5*/MyWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
return /*[0*/const Placeholder()/*0]*/;
|
||||
}
|
||||
}''');
|
||||
expect(snippet.change.selection!.file, testFile);
|
||||
expect(snippet.change.selection!.offset, 358);
|
||||
expect(snippet.change.selectionLength, 19);
|
||||
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
|
||||
{
|
||||
'positions': [
|
||||
{'file': testFile, 'offset': 115},
|
||||
{'file': testFile, 'offset': 157},
|
||||
{'file': testFile, 'offset': 201},
|
||||
{'file': testFile, 'offset': 229},
|
||||
{'file': testFile, 'offset': 256},
|
||||
{'file': testFile, 'offset': 284},
|
||||
],
|
||||
'length': 8,
|
||||
'suggestions': []
|
||||
}
|
||||
]);
|
||||
assertFlutterSnippetChange(snippet.change, 'MyWidget', expected);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-37
@@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:analysis_server/src/protocol_server.dart';
|
||||
import 'package:analysis_server/src/services/snippets/dart/flutter_stateful_widget_with_animation.dart';
|
||||
import 'package:analyzer/src/test_utilities/test_code_format.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
@@ -39,11 +40,7 @@ class FlutterStatefulWidgetWithAnimationControllerTest
|
||||
code = SourceEdit.applySequence(code, edit.edits);
|
||||
}
|
||||
expect(code, '''
|
||||
import 'package:flutter/src/animation/animation_controller.dart';
|
||||
import 'package:flutter/src/foundation/key.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
import 'package:flutter/src/widgets/ticker_provider.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MyWidget extends StatefulWidget {
|
||||
const MyWidget({Key? key}) : super(key: key);
|
||||
@@ -87,25 +84,17 @@ class _MyWidgetState extends State<MyWidget>
|
||||
final snippet = await expectValidSnippet('^');
|
||||
expect(snippet.prefix, prefix);
|
||||
expect(snippet.label, label);
|
||||
var code = '';
|
||||
expect(snippet.change.edits, hasLength(1));
|
||||
for (var edit in snippet.change.edits) {
|
||||
code = SourceEdit.applySequence(code, edit.edits);
|
||||
}
|
||||
expect(code, '''
|
||||
import 'package:flutter/src/animation/animation_controller.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
import 'package:flutter/src/widgets/ticker_provider.dart';
|
||||
final expected = TestCode.parse('''
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MyWidget extends StatefulWidget {
|
||||
const MyWidget({super.key});
|
||||
class /*0*/MyWidget extends StatefulWidget {
|
||||
const /*1*/MyWidget({super.key});
|
||||
|
||||
@override
|
||||
State<MyWidget> createState() => _MyWidgetState();
|
||||
State</*2*/MyWidget> createState() => _/*3*/MyWidgetState();
|
||||
}
|
||||
|
||||
class _MyWidgetState extends State<MyWidget>
|
||||
class _/*4*/MyWidgetState extends State</*5*/MyWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@@ -123,25 +112,9 @@ class _MyWidgetState extends State<MyWidget>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
return /*[0*/const Placeholder()/*0]*/;
|
||||
}
|
||||
}''');
|
||||
expect(snippet.change.selection!.file, testFile);
|
||||
expect(snippet.change.selection!.offset, 761);
|
||||
expect(snippet.change.selectionLength, 19);
|
||||
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
|
||||
{
|
||||
'positions': [
|
||||
{'file': testFile, 'offset': 240},
|
||||
{'file': testFile, 'offset': 282},
|
||||
{'file': testFile, 'offset': 326},
|
||||
{'file': testFile, 'offset': 354},
|
||||
{'file': testFile, 'offset': 381},
|
||||
{'file': testFile, 'offset': 409},
|
||||
],
|
||||
'length': 8,
|
||||
'suggestions': []
|
||||
}
|
||||
]);
|
||||
assertFlutterSnippetChange(snippet.change, 'MyWidget', expected);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-37
@@ -2,8 +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:analysis_server/src/protocol_server.dart';
|
||||
import 'package:analysis_server/src/services/snippets/dart/flutter_stateless_widget.dart';
|
||||
import 'package:analyzer/src/test_utilities/test_code_format.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
@@ -32,24 +32,18 @@ class FlutterStatelessWidgetTest extends FlutterSnippetProducerTest {
|
||||
final snippet = await expectValidSnippet('^');
|
||||
expect(snippet.prefix, prefix);
|
||||
expect(snippet.label, label);
|
||||
var code = '';
|
||||
expect(snippet.change.edits, hasLength(1));
|
||||
for (var edit in snippet.change.edits) {
|
||||
code = SourceEdit.applySequence(code, edit.edits);
|
||||
}
|
||||
expect(code, '''
|
||||
import 'package:flutter/src/foundation/key.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
final expected = TestCode.parse('''
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MyWidget extends StatelessWidget {
|
||||
const MyWidget({Key? key}) : super(key: key);
|
||||
class /*0*/MyWidget extends StatelessWidget {
|
||||
const /*1*/MyWidget({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
return /*[0*/const Placeholder()/*0]*/;
|
||||
}
|
||||
}''');
|
||||
assertFlutterSnippetChange(snippet.change, 'MyWidget', expected);
|
||||
}
|
||||
|
||||
Future<void> test_notValid_notFlutterProject() async {
|
||||
@@ -64,35 +58,17 @@ class MyWidget extends StatelessWidget {
|
||||
final snippet = await expectValidSnippet('^');
|
||||
expect(snippet.prefix, prefix);
|
||||
expect(snippet.label, label);
|
||||
var code = '';
|
||||
expect(snippet.change.edits, hasLength(1));
|
||||
for (var edit in snippet.change.edits) {
|
||||
code = SourceEdit.applySequence(code, edit.edits);
|
||||
}
|
||||
expect(code, '''
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
final expected = TestCode.parse('''
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MyWidget extends StatelessWidget {
|
||||
const MyWidget({super.key});
|
||||
class /*0*/MyWidget extends StatelessWidget {
|
||||
const /*1*/MyWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
return /*[0*/const Placeholder()/*0]*/;
|
||||
}
|
||||
}''');
|
||||
expect(snippet.change.selection!.file, testFile);
|
||||
expect(snippet.change.selection!.offset, 244);
|
||||
expect(snippet.change.selectionLength, 19);
|
||||
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
|
||||
{
|
||||
'positions': [
|
||||
{'file': testFile, 'offset': 115},
|
||||
{'file': testFile, 'offset': 158},
|
||||
],
|
||||
'length': 8,
|
||||
'suggestions': []
|
||||
}
|
||||
]);
|
||||
assertFlutterSnippetChange(snippet.change, 'MyWidget', expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +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/protocol_server.dart';
|
||||
import 'package:analysis_server/src/services/snippets/dart_snippet_request.dart';
|
||||
import 'package:analysis_server/src/services/snippets/snippet.dart';
|
||||
import 'package:analysis_server/src/services/snippets/snippet_manager.dart';
|
||||
import 'package:analyzer/src/test_utilities/test_code_format.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../../abstract_single_unit.dart';
|
||||
@@ -52,6 +54,33 @@ abstract class DartSnippetProducerTest extends AbstractSingleUnitTest {
|
||||
}
|
||||
|
||||
abstract class FlutterSnippetProducerTest extends DartSnippetProducerTest {
|
||||
/// Asserts that [change] matches the code in [expected], has a selection
|
||||
/// matching its range and a single linked edit group containing all of its
|
||||
/// positions.
|
||||
void assertFlutterSnippetChange(
|
||||
SourceChange change,
|
||||
String linkedGroupText,
|
||||
TestCode expected,
|
||||
) {
|
||||
expect(change.edits, hasLength(1));
|
||||
final code = SourceEdit.applySequence('', change.edits.single.edits);
|
||||
expect(code, expected.code);
|
||||
|
||||
expect(change.selection!.file, testFile);
|
||||
expect(change.selection!.offset, expected.range.sourceRange.offset);
|
||||
expect(change.selectionLength, expected.range.sourceRange.length);
|
||||
expect(change.linkedEditGroups.map((group) => group.toJson()), [
|
||||
{
|
||||
'positions': [
|
||||
for (final position in expected.positions)
|
||||
{'file': testFile, 'offset': position.offset},
|
||||
],
|
||||
'length': linkedGroupText.length,
|
||||
'suggestions': []
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/// Checks snippets can produce edits where the imports and snippet will be
|
||||
/// inserted at the same location.
|
||||
///
|
||||
|
||||
@@ -18,6 +18,14 @@ abstract class FileStateFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a filter of files in the package named [packageName].
|
||||
factory FileStateFilter.packageName(
|
||||
String? packageName, {
|
||||
required bool excludeSrc,
|
||||
}) {
|
||||
return _PackageNameFilter(packageName, excludeSrc: excludeSrc);
|
||||
}
|
||||
|
||||
bool shouldInclude(FileState file);
|
||||
}
|
||||
|
||||
@@ -32,6 +40,22 @@ class _AnyFilter implements FileStateFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches any file in the package [packageName].
|
||||
///
|
||||
/// If [packageName] is `null`, matches files that also have no `packageName`.
|
||||
class _PackageNameFilter implements FileStateFilter {
|
||||
final String? packageName;
|
||||
final bool excludeSrc;
|
||||
|
||||
_PackageNameFilter(this.packageName, {required this.excludeSrc});
|
||||
|
||||
@override
|
||||
bool shouldInclude(FileState file) {
|
||||
var uri = file.uriProperties;
|
||||
return uri.packageName == packageName && !(uri.isSrc && excludeSrc);
|
||||
}
|
||||
}
|
||||
|
||||
class _PubFilter implements FileStateFilter {
|
||||
final PubWorkspacePackage targetPackage;
|
||||
final String? targetPackageName;
|
||||
|
||||
+44
-10
@@ -19,6 +19,42 @@ class TopLevelDeclarations {
|
||||
return analysisContext as DriverBasedAnalysisContext;
|
||||
}
|
||||
|
||||
/// Return the first public library that that exports (but does not necessary
|
||||
/// declare) [element].
|
||||
Future<LibraryElement?> publiclyExporting(Element element) async {
|
||||
var declarationFilePath = element.source?.fullName;
|
||||
if (declarationFilePath == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var analysisDriver = _analysisContext.driver;
|
||||
var fsState = analysisDriver.fsState;
|
||||
await analysisDriver.discoverAvailableFiles();
|
||||
|
||||
var declarationFile = fsState.getFileForPath(declarationFilePath);
|
||||
var declarationPackage = declarationFile.uriProperties.packageName;
|
||||
|
||||
for (var file in fsState.knownFiles.toList()) {
|
||||
var uri = file.uriProperties;
|
||||
// Only search the package that contains the declaration and its public
|
||||
// libraries.
|
||||
if (uri.packageName != declarationPackage || uri.isSrc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var elementResult = await analysisDriver.getLibraryByUri(file.uriStr);
|
||||
if (elementResult is! LibraryElementResult) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_findElement(elementResult.element, element.displayName) != null) {
|
||||
return elementResult.element;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Return the mapping from a library (that is available to this context) to
|
||||
/// a top-level declaration that is exported (not necessary declared) by this
|
||||
/// library, and has the requested base name. For getters and setters the
|
||||
@@ -55,17 +91,15 @@ class TopLevelDeclarations {
|
||||
LibraryElement libraryElement,
|
||||
String baseName,
|
||||
) {
|
||||
void addSingle(String name) {
|
||||
var element = libraryElement.exportNamespace.get(name);
|
||||
if (element is PropertyAccessorElement) {
|
||||
element = element.variable;
|
||||
}
|
||||
if (element != null) {
|
||||
result[libraryElement] = element;
|
||||
}
|
||||
var element = _findElement(libraryElement, baseName);
|
||||
if (element != null) {
|
||||
result[libraryElement] = element;
|
||||
}
|
||||
}
|
||||
|
||||
addSingle(baseName);
|
||||
addSingle('$baseName=');
|
||||
static Element? _findElement(LibraryElement libraryElement, String name) {
|
||||
var element = libraryElement.exportNamespace.get(name) ??
|
||||
libraryElement.exportNamespace.get('$name=');
|
||||
return element is PropertyAccessorElement ? element.variable : element;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
@@ -118,8 +119,8 @@ class ChangeBuilderImpl implements ChangeBuilder {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addDartFileEdit(
|
||||
String path, void Function(DartFileEditBuilder builder) buildFileEdit,
|
||||
Future<void> addDartFileEdit(String path,
|
||||
FutureOr<void> Function(DartFileEditBuilder builder) buildFileEdit,
|
||||
{ImportPrefixGenerator? importPrefixGenerator,
|
||||
bool createEditsForImports = true}) async {
|
||||
if (_genericFileEditBuilders.containsKey(path)) {
|
||||
@@ -147,7 +148,7 @@ class ChangeBuilderImpl implements ChangeBuilder {
|
||||
}
|
||||
if (builder != null) {
|
||||
builder.importPrefixGenerator = importPrefixGenerator;
|
||||
buildFileEdit(builder);
|
||||
await buildFileEdit(builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:analyzer/dart/element/type_provider.dart';
|
||||
import 'package:analyzer/src/dart/ast/utilities.dart';
|
||||
import 'package:analyzer/src/dart/element/type.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:analyzer/src/services/top_level_declarations.dart';
|
||||
import 'package:analyzer_plugin/protocol/protocol_common.dart'
|
||||
hide Element, ElementKind;
|
||||
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core.dart';
|
||||
@@ -1172,7 +1173,7 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
|
||||
if (import != null) {
|
||||
var prefix = import.prefix;
|
||||
if (prefix != null) {
|
||||
write(prefix.element.displayName);
|
||||
write(prefix);
|
||||
write('.');
|
||||
}
|
||||
} else {
|
||||
@@ -1401,7 +1402,11 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
|
||||
/// A mapping from libraries that need to be imported in order to make visible
|
||||
/// the names used in generated code, to information about these imports.
|
||||
Map<Uri, _LibraryToImport> librariesToImport = {};
|
||||
Map<Uri, _LibraryImport> librariesToImport = {};
|
||||
|
||||
/// A mapping of [Element]s to pending imports that will be added to make
|
||||
/// them visible in the generated code.
|
||||
final Map<Element, _LibraryImport> _elementLibrariesToImport = {};
|
||||
|
||||
/// Initialize a newly created builder to build a source file edit within the
|
||||
/// change being built by the given [changeBuilder]. The file being edited has
|
||||
@@ -1470,6 +1475,9 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
for (var entry in librariesToImport.entries) {
|
||||
copy.librariesToImport[entry.key] = entry.value;
|
||||
}
|
||||
for (var entry in _elementLibrariesToImport.entries) {
|
||||
copy._elementLibrariesToImport[entry.key] = entry.value;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -1520,6 +1528,34 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
);
|
||||
}
|
||||
|
||||
/// Arrange to have an import added that makes [element] available.
|
||||
///
|
||||
/// If [element] is already available in the current library, does nothing.
|
||||
///
|
||||
/// If the library [element] is declared in is inside the `src` folder, will
|
||||
/// try to locate a public URI to import instead.
|
||||
Future<void> importElementLibrary(Element element) async {
|
||||
// TODO(dantup): Add the ability to pass a cache in to this function so
|
||||
// multiple callers can avoid looking up the same elements.
|
||||
if (_isDefinedLocally(element) || _getImportElement(element) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var libraryWithElement =
|
||||
await TopLevelDeclarations(resolvedUnit).publiclyExporting(element);
|
||||
if (libraryWithElement != null) {
|
||||
_elementLibrariesToImport[element] =
|
||||
_importLibrary(libraryWithElement.source.uri);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we didn't find one, use the original URI.
|
||||
var uri = element.source?.uri;
|
||||
if (uri != null) {
|
||||
_importLibrary(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String importLibrary(Uri uri, {String? prefix}) {
|
||||
return _importLibrary(uri, prefix: prefix).uriText;
|
||||
@@ -1590,7 +1626,7 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
}
|
||||
|
||||
/// Adds edits ensure that all the [imports] are imported into the library.
|
||||
void _addLibraryImports(Iterable<_LibraryToImport> imports) {
|
||||
void _addLibraryImports(Iterable<_LibraryImport> imports) {
|
||||
// Prepare information about existing imports.
|
||||
LibraryDirective? libraryDirective;
|
||||
var importDirectives = <ImportDirective>[];
|
||||
@@ -1614,7 +1650,7 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
importList.sort((a, b) => a.uriText.compareTo(b.uriText));
|
||||
|
||||
var quote = codeStyleOptions.preferredQuoteForUris(importDirectives);
|
||||
void writeImport(EditBuilder builder, _LibraryToImport import) {
|
||||
void writeImport(EditBuilder builder, _LibraryImport import) {
|
||||
builder.write('import $quote');
|
||||
builder.write(import.uriText);
|
||||
builder.write(quote);
|
||||
@@ -1817,17 +1853,21 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the import element used to import the given [element] into the
|
||||
/// target library, or `null` if the element was not imported, such as when
|
||||
/// the element is declared in the same library.
|
||||
LibraryImportElement? _getImportElement(Element element) {
|
||||
/// Return information about the library used to import the given [element]
|
||||
/// into the target library, or `null` if the element was not imported, such
|
||||
/// as when the element is declared in the same library.
|
||||
///
|
||||
/// The result may be an existing import, or one that is pending.
|
||||
_LibraryImport? _getImportElement(Element element) {
|
||||
for (var import in resolvedUnit.libraryElement.libraryImports) {
|
||||
var definedNames = import.namespace.definedNames;
|
||||
if (definedNames.containsValue(element)) {
|
||||
return import;
|
||||
return _LibraryImport(import.librarySource.uri.toString(),
|
||||
import.prefix?.element.displayName);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return _elementLibrariesToImport[element];
|
||||
}
|
||||
|
||||
Iterable<LibraryImportElement> _getImportsForUri(Uri uri) sync* {
|
||||
@@ -1878,7 +1918,7 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
///
|
||||
/// [uri] may be converted from an absolute URI to a relative URI depending on
|
||||
/// user preferences/lints unless [forceAbsolute] or [forceRelative] are `true`.
|
||||
_LibraryToImport _importLibrary(
|
||||
_LibraryImport _importLibrary(
|
||||
Uri uri, {
|
||||
String? prefix,
|
||||
bool forceAbsolute = false,
|
||||
@@ -1890,7 +1930,7 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
|
||||
forceAbsolute: forceAbsolute, forceRelative: forceRelative);
|
||||
prefix ??=
|
||||
importPrefixGenerator != null ? importPrefixGenerator!(uri) : null;
|
||||
import = _LibraryToImport(uriText, prefix);
|
||||
import = _LibraryImport(uriText, prefix);
|
||||
(libraryChangeBuilder ?? this).librariesToImport[uri] = import;
|
||||
}
|
||||
return import;
|
||||
@@ -1995,19 +2035,19 @@ class _EnclosingElementFinder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about a new library to import.
|
||||
class _LibraryToImport {
|
||||
/// Information about a library import.
|
||||
class _LibraryImport {
|
||||
final String uriText;
|
||||
final String? prefix;
|
||||
|
||||
_LibraryToImport(this.uriText, this.prefix);
|
||||
_LibraryImport(this.uriText, this.prefix);
|
||||
|
||||
@override
|
||||
int get hashCode => uriText.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
return other is _LibraryToImport &&
|
||||
return other is _LibraryImport &&
|
||||
other.uriText == uriText &&
|
||||
other.prefix == prefix;
|
||||
}
|
||||
|
||||
@@ -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 'dart:async';
|
||||
|
||||
import 'package:analyzer/dart/analysis/session.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
||||
@@ -40,8 +42,8 @@ abstract class ChangeBuilder {
|
||||
///
|
||||
/// Setting [createEditsForImports] to `false` will prevent edits being
|
||||
/// produced to add `import` statements for any unimported types.
|
||||
Future<void> addDartFileEdit(
|
||||
String path, void Function(DartFileEditBuilder builder) buildFileEdit,
|
||||
Future<void> addDartFileEdit(String path,
|
||||
FutureOr<void> Function(DartFileEditBuilder builder) buildFileEdit,
|
||||
{ImportPrefixGenerator importPrefixGenerator,
|
||||
bool createEditsForImports = true});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user