Skip to content

Commit bb90c40

Browse files
keertipcommit-bot@chromium.org
authored andcommitted
Add a method to FileResolver to find references.
Change-Id: I48cd2dec633e16d6120c921df1debc8f012fd41f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/197103 Commit-Queue: Keerti Parthasarathy <keertip@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
1 parent cfe24a0 commit bb90c40

File tree

5 files changed

+533
-5
lines changed

5 files changed

+533
-5
lines changed

pkg/analyzer/lib/src/dart/micro/library_graph.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,10 @@ class FileSystemState {
518518
/// to batch file reads in systems where file fetches are expensive.
519519
final void Function(List<String> paths)? prefetchFiles;
520520

521+
/// A function that returns true if the given file path is likely to be that
522+
/// of a file that is generated.
523+
final bool Function(String path)? isGenerated;
524+
521525
final FileSystemStateTimers timers2 = FileSystemStateTimers();
522526

523527
final FileSystemStateTestView testView = FileSystemStateTestView();
@@ -533,6 +537,7 @@ class FileSystemState {
533537
this.featureSetProvider,
534538
this.getFileDigest,
535539
this.prefetchFiles,
540+
this.isGenerated,
536541
);
537542

538543
/// Update the state to reflect the fact that the file with the given [path]
@@ -662,6 +667,19 @@ class FileSystemState {
662667
return file;
663668
}
664669

670+
/// Returns a list of files whose contents contains the given string.
671+
/// Generated files are not included in the search.
672+
List<String> getFilesContaining(String value) {
673+
var result = <String>[];
674+
_pathToFile.forEach((path, file) {
675+
var genFile = isGenerated == null ? false : isGenerated!(path);
676+
if (!genFile && file.getContent().contains(value)) {
677+
result.add(path);
678+
}
679+
});
680+
return result;
681+
}
682+
665683
String? getPathForUri(Uri uri) {
666684
var source = _sourceFactory.forUri2(uri);
667685
if (source == null) {

pkg/analyzer/lib/src/dart/micro/resolve_file.dart

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import 'package:analyzer/src/dart/analysis/experiments.dart';
1818
import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
1919
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
2020
import 'package:analyzer/src/dart/analysis/results.dart';
21+
import 'package:analyzer/src/dart/ast/utilities.dart';
2122
import 'package:analyzer/src/dart/micro/analysis_context.dart';
2223
import 'package:analyzer/src/dart/micro/cider_byte_store.dart';
2324
import 'package:analyzer/src/dart/micro/library_analyzer.dart';
2425
import 'package:analyzer/src/dart/micro/library_graph.dart';
26+
import 'package:analyzer/src/dart/micro/utils.dart';
2527
import 'package:analyzer/src/exception/exception.dart';
2628
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
2729
import 'package:analyzer/src/generated/source.dart';
@@ -36,11 +38,25 @@ import 'package:analyzer/src/task/options.dart';
3638
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
3739
import 'package:analyzer/src/util/performance/operation_performance.dart';
3840
import 'package:analyzer/src/workspace/workspace.dart';
41+
import 'package:collection/collection.dart';
3942
import 'package:yaml/yaml.dart';
4043

4144
const M = 1024 * 1024 /*1 MiB*/;
4245
const memoryCacheSize = 200 * M;
4346

47+
class CiderSearchMatch {
48+
final String path;
49+
final List<int> offsets;
50+
51+
CiderSearchMatch(this.path, this.offsets);
52+
53+
@override
54+
bool operator ==(Object object) =>
55+
object is CiderSearchMatch &&
56+
path == object.path &&
57+
const ListEquality<int>().equals(offsets, object.offsets);
58+
}
59+
4460
class FileContext {
4561
final AnalysisOptionsImpl analysisOptions;
4662
final FileState file;
@@ -54,13 +70,15 @@ class FileResolver {
5470
CiderByteStore byteStore;
5571
final SourceFactory sourceFactory;
5672

57-
/*
58-
* A function that returns the digest for a file as a String. The function
59-
* returns a non null value, can return an empty string if file does
60-
* not exist/has no contents.
61-
*/
73+
/// A function that returns the digest for a file as a String. The function
74+
/// returns a non null value, can return an empty string if file does
75+
/// not exist/has no contents.
6276
final String Function(String path) getFileDigest;
6377

78+
/// A function that returns true if the given file path is likely to be that
79+
/// of a file that is generated.
80+
final bool Function(String path)? isGenerated;
81+
6482
/// A function that fetches the given list of files. This function can be used
6583
/// to batch file reads in systems where file fetches are expensive.
6684
final void Function(List<String> paths)? prefetchFiles;
@@ -96,6 +114,7 @@ class FileResolver {
96114
String Function(String path) getFileDigest,
97115
void Function(List<String> paths)? prefetchFiles, {
98116
required Workspace workspace,
117+
bool Function(String path)? isGenerated,
99118
@deprecated Duration? libraryContextResetTimeout,
100119
}) : this.from(
101120
logger: logger,
@@ -104,6 +123,8 @@ class FileResolver {
104123
getFileDigest: getFileDigest,
105124
prefetchFiles: prefetchFiles,
106125
workspace: workspace,
126+
isGenerated: isGenerated,
127+
107128
// ignore: deprecated_member_use_from_same_package
108129
libraryContextResetTimeout: libraryContextResetTimeout,
109130
);
@@ -115,6 +136,7 @@ class FileResolver {
115136
required String Function(String path) getFileDigest,
116137
required void Function(List<String> paths)? prefetchFiles,
117138
required Workspace workspace,
139+
bool Function(String path)? isGenerated,
118140
CiderByteStore? byteStore,
119141
@deprecated Duration? libraryContextResetTimeout,
120142
}) : logger = logger,
@@ -123,6 +145,7 @@ class FileResolver {
123145
getFileDigest = getFileDigest,
124146
prefetchFiles = prefetchFiles,
125147
workspace = workspace,
148+
isGenerated = isGenerated,
126149
byteStore = byteStore ?? CiderCachedByteStore(memoryCacheSize);
127150

128151
/// Update the resolver to reflect the fact that the file with the given
@@ -164,6 +187,31 @@ class FileResolver {
164187
@deprecated
165188
void dispose() {}
166189

190+
/// Looks for references to the Element at the given offset and path. All the
191+
/// files currently cached by the resolver are searched, generated files are
192+
/// ignored.
193+
List<CiderSearchMatch> findReferences(int offset, String path,
194+
{OperationPerformanceImpl? performance}) {
195+
var references = <CiderSearchMatch>[];
196+
var unit = resolve(path: path);
197+
var node = NodeLocator(offset).searchWithin(unit.unit);
198+
var element = getElementOfNode(node);
199+
if (element != null) {
200+
// TODO(keertip): check if element is named constructor.
201+
var result = fsState!.getFilesContaining(element.displayName);
202+
result.forEach((filePath) {
203+
var resolved = resolve(path: filePath);
204+
var collector = ReferencesCollector(element);
205+
resolved.unit?.accept(collector);
206+
var offsets = collector.offsets;
207+
if (offsets.isNotEmpty) {
208+
references.add(CiderSearchMatch(filePath, offsets));
209+
}
210+
});
211+
}
212+
return references;
213+
}
214+
167215
ErrorsResult getErrors({
168216
required String path,
169217
OperationPerformanceImpl? performance,
@@ -555,6 +603,7 @@ class FileResolver {
555603
featureSetProvider,
556604
getFileDigest,
557605
prefetchFiles,
606+
isGenerated,
558607
);
559608
}
560609

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) 2021, 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:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/ast/visitor.dart';
7+
import 'package:analyzer/dart/element/element.dart';
8+
import 'package:analyzer/src/dart/ast/element_locator.dart';
9+
10+
/// Return the [Element] of the given [node], or `null` if [node] is `null` or
11+
/// does not have an element.
12+
Element? getElementOfNode(AstNode? node) {
13+
if (node == null) {
14+
return null;
15+
}
16+
if (node is SimpleIdentifier && node.parent is LibraryIdentifier) {
17+
node = node.parent;
18+
}
19+
if (node is LibraryIdentifier) {
20+
node = node.parent;
21+
}
22+
if (node is StringLiteral && node.parent is UriBasedDirective) {
23+
return null;
24+
}
25+
var element = ElementLocator.locate(node);
26+
if (node is SimpleIdentifier && element is PrefixElement) {
27+
var parent = node.parent;
28+
if (parent is ImportDirective) {
29+
element = parent.element;
30+
} else {
31+
element = _getImportElementInfo(node);
32+
}
33+
}
34+
return element;
35+
}
36+
37+
/// Return the [ImportElement] that declared [prefix] and imports [element].
38+
///
39+
/// [libraryElement] - the [LibraryElement] where reference is.
40+
/// [prefix] - the import prefix, maybe `null`.
41+
/// [element] - the referenced element.
42+
/// [importElementsMap] - the cache of [Element]s imported by [ImportElement]s.
43+
ImportElement? _getImportElement(LibraryElement libraryElement, String prefix,
44+
Element element, Map<ImportElement, Set<Element>> importElementsMap) {
45+
if (element.enclosingElement is! CompilationUnitElement) {
46+
return null;
47+
}
48+
var usedLibrary = element.library;
49+
// find ImportElement that imports used library with used prefix
50+
List<ImportElement>? candidates;
51+
for (var importElement in libraryElement.imports) {
52+
// required library
53+
if (importElement.importedLibrary != usedLibrary) {
54+
continue;
55+
}
56+
// required prefix
57+
var prefixElement = importElement.prefix;
58+
if (prefixElement == null) {
59+
continue;
60+
}
61+
if (prefix != prefixElement.name) {
62+
continue;
63+
}
64+
// no combinators => only possible candidate
65+
if (importElement.combinators.isEmpty) {
66+
return importElement;
67+
}
68+
// OK, we have candidate
69+
candidates ??= [];
70+
candidates.add(importElement);
71+
}
72+
// no candidates, probably element is defined in this library
73+
if (candidates == null) {
74+
return null;
75+
}
76+
// one candidate
77+
if (candidates.length == 1) {
78+
return candidates[0];
79+
}
80+
// ensure that each ImportElement has set of elements
81+
for (var importElement in candidates) {
82+
if (importElementsMap.containsKey(importElement)) {
83+
continue;
84+
}
85+
var namespace = importElement.namespace;
86+
var elements = Set<Element>.from(namespace.definedNames.values);
87+
importElementsMap[importElement] = elements;
88+
}
89+
// use import namespace to choose correct one
90+
for (var entry in importElementsMap.entries) {
91+
var importElement = entry.key;
92+
var elements = entry.value;
93+
if (elements.contains(element)) {
94+
return importElement;
95+
}
96+
}
97+
// not found
98+
return null;
99+
}
100+
101+
/// Returns the [ImportElement] that is referenced by [prefixNode] with a
102+
/// [PrefixElement], maybe `null`.
103+
ImportElement? _getImportElementInfo(SimpleIdentifier prefixNode) {
104+
// prepare environment
105+
var parent = prefixNode.parent;
106+
var unit = prefixNode.thisOrAncestorOfType<CompilationUnit>();
107+
var libraryElement = unit?.declaredElement?.library;
108+
if (libraryElement == null) {
109+
return null;
110+
}
111+
// prepare used element
112+
Element? usedElement;
113+
if (parent is PrefixedIdentifier) {
114+
var prefixed = parent;
115+
if (prefixed.prefix == prefixNode) {
116+
usedElement = prefixed.staticElement;
117+
}
118+
} else if (parent is MethodInvocation) {
119+
var invocation = parent;
120+
if (invocation.target == prefixNode) {
121+
usedElement = invocation.methodName.staticElement;
122+
}
123+
}
124+
// we need used Element
125+
if (usedElement == null) {
126+
return null;
127+
}
128+
// find ImportElement
129+
var prefix = prefixNode.name;
130+
var importElementsMap = <ImportElement, Set<Element>>{};
131+
return _getImportElement(
132+
libraryElement, prefix, usedElement, importElementsMap);
133+
}
134+
135+
class ReferencesCollector extends GeneralizingAstVisitor<void> {
136+
final Element element;
137+
final List<int> offsets = [];
138+
139+
ReferencesCollector(this.element);
140+
141+
@override
142+
void visitAssignmentExpression(AssignmentExpression node) {
143+
if (node.writeElement != null &&
144+
node.writeElement is PropertyAccessorElement) {
145+
var property = node.writeElement as PropertyAccessorElement;
146+
if (property.variable == element || property == element) {
147+
if (node.leftHandSide is SimpleIdentifier) {
148+
offsets.add(node.leftHandSide.offset);
149+
} else if (node.leftHandSide is PrefixedIdentifier) {
150+
var prefixIdentifier = node.leftHandSide as PrefixedIdentifier;
151+
offsets.add(prefixIdentifier.identifier.offset);
152+
} else if (node.leftHandSide is PropertyAccess) {
153+
var accessor = node.leftHandSide as PropertyAccess;
154+
offsets.add(accessor.propertyName.offset);
155+
}
156+
}
157+
}
158+
if (node.readElement != null &&
159+
node.readElement is PropertyAccessorElement) {
160+
var property = node.readElement as PropertyAccessorElement;
161+
if (property.variable == element) {
162+
offsets.add(node.rightHandSide.offset);
163+
}
164+
}
165+
}
166+
167+
@override
168+
void visitSimpleIdentifier(SimpleIdentifier node) {
169+
var e = node.staticElement;
170+
if (e == element) {
171+
offsets.add(node.offset);
172+
} else if (e is PropertyAccessorElement && e.variable == element) {
173+
offsets.add(node.offset);
174+
}
175+
}
176+
}

pkg/analyzer/test/src/dart/micro/file_resolution.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class FileResolutionTest with ResourceProviderMixin, ResolutionTest {
5454
getFileDigest: (String path) => _getDigest(path),
5555
workspace: workspace,
5656
prefetchFiles: null,
57+
isGenerated: null,
5758
);
5859
fileResolver.testView = FileResolverTestView();
5960
}

0 commit comments

Comments
 (0)