blob: ff4c6b1d4d58d2abc38cd34607946eef95c08439 [file]
// Copyright (c) 2017, 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:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/workspace/blaze.dart';
import 'package:path/path.dart' show relative;
import 'schema.dart' as schema;
/// Given some [ConstructorElement], this method returns '<class-name>' as the
/// name of the constructor, unless the constructor is a named constructor in
/// which '<class-name>.<constructor-name>' is returned.
String _computeConstructorElementName(ConstructorElement element) {
var name = element.enclosingElement.name;
var constructorName = element.name;
if (constructorName.isNotEmpty) {
name = '$name.$constructorName';
}
return name;
}
String? _getNodeKind(Element e) {
if (e is FieldElement && e.isEnumConstant) {
// FieldElement is a kind of VariableElement, so this test case must be
// before the e is VariableElement check.
return schema.CONSTANT_KIND;
} else if (e is VariableElement || e is PrefixElement) {
return schema.VARIABLE_KIND;
} else if (e is ExecutableElement) {
return schema.FUNCTION_KIND;
} else if (e is InterfaceElement || e is TypeParameterElement) {
// TODO(jwren): this should be using absvar instead, see
// https://kythe.io/docs/schema/#absvar
return schema.RECORD_KIND;
}
return null;
}
String _getPath(ResourceProvider provider, Element? e,
{String? sdkRootPath, String? corpus}) {
// TODO(jwren): This method simply serves to provide the WORKSPACE relative
// path for sources in Elements, it needs to be written in a more robust way.
// TODO(jwren): figure out what source generates a e != null, but
// e.source == null to ensure that it is not a bug somewhere in the stack.
var source = e?.source;
if (source == null) {
// null sometimes when the element is used to generate the node type
// "dynamic"
return '';
}
if (sdkRootPath != null) {
final uri = source.uri;
if (uri.isScheme('dart')) {
final pathSegments = uri.pathSegments;
if (pathSegments.length == 1) {
final libraryName = pathSegments.single;
return '$sdkRootPath/lib/$libraryName/$libraryName.dart';
} else {
return '$sdkRootPath/lib/${uri.path}';
}
}
return relative(source.fullName, from: '/$corpus/');
}
var path = source.fullName;
var blazeWorkspace = BlazeWorkspace.find(provider, path);
if (blazeWorkspace != null) {
return provider.pathContext.relative(path, from: blazeWorkspace.root);
}
if (path.lastIndexOf('CORPUS_NAME') != -1) {
return path.substring(path.lastIndexOf('CORPUS_NAME') + 12);
}
return path;
}
/// If a non-null element is passed, the [_SignatureElementVisitor] is used to
/// generate and return a [String] signature, otherwise [schema.DYNAMIC_KIND] is
/// returned.
String _getSignature(
ResourceProvider provider, Element? element, String nodeKind, String corpus,
{String? sdkRootPath}) {
assert(nodeKind != schema.ANCHOR_KIND); // Call _getAnchorSignature instead
if (element == null) {
return schema.DYNAMIC_KIND;
}
if (element is CompilationUnitElement) {
return _getPath(provider, element,
sdkRootPath: sdkRootPath, corpus: corpus);
}
return '$nodeKind:${element.accept(_SignatureElementVisitor.instance)}';
}
/// A helper class for getting the Kythe uri's for elements for querying
/// Kythe from Cider.
class CiderKytheHelper {
final String sdkRootPath;
final String corpus;
final ResourceProvider resourceProvider;
CiderKytheHelper(this.resourceProvider, this.corpus, this.sdkRootPath);
/// Returns a URI that can be used to query Kythe.
String toKytheUri(Element e) {
var nodeKind = _getNodeKind(e) ?? schema.RECORD_KIND;
var vname = _vNameFromElement(e, nodeKind);
return 'kythe://$corpus?lang=dart?path=${vname.path}#${vname.signature}';
}
/// Given some [Element] and Kythe node kind, this method generates and
/// returns the [_KytheVName].
_KytheVName _vNameFromElement(Element? e, String nodeKind) {
assert(nodeKind != schema.FILE_KIND);
// general case
return _KytheVName(
path: _getPath(resourceProvider, e,
sdkRootPath: sdkRootPath, corpus: corpus),
signature: _getSignature(resourceProvider, e, nodeKind, corpus,
sdkRootPath: sdkRootPath),
);
}
}
class _KytheVName {
final String path;
final String signature;
_KytheVName({
required this.path,
required this.signature,
});
}
/// This visitor class should be used by [_getSignature].
///
/// This visitor is an [GeneralizingElementVisitor] which builds up a [String]
/// signature for a given [Element], uniqueness is guaranteed within the
/// enclosing file.
class _SignatureElementVisitor
extends GeneralizingElementVisitor<StringBuffer> {
static _SignatureElementVisitor instance = _SignatureElementVisitor();
@override
StringBuffer visitCompilationUnitElement(CompilationUnitElement element) {
return StringBuffer();
}
@override
StringBuffer visitElement(Element element) {
assert(element is! MultiplyInheritedExecutableElement);
var enclosingElt = element.enclosingElement!;
var buffer = enclosingElt.accept(this)!;
if (buffer.isNotEmpty) {
buffer.write('#');
}
if (element is MethodElement &&
element.name == '-' &&
element.parameters.length == 1) {
buffer.write('unary-');
} else if (element is ConstructorElement) {
buffer.write(_computeConstructorElementName(element));
} else {
buffer.write(element.name);
}
if (enclosingElt is ExecutableElement) {
buffer
..write('@')
..write(element.nameOffset - enclosingElt.nameOffset);
}
return buffer;
}
@override
StringBuffer visitLibraryElement(LibraryElement element) {
return StringBuffer('library:${element.displayName}');
}
@override
StringBuffer visitTypeParameterElement(TypeParameterElement element) {
// It is legal to have a named constructor with the same name as a type
// parameter. So we distinguish them by using '.' between the class (or
// typedef) name and the type parameter name.
return element.enclosingElement!.accept(this)!
..write('.')
..write(element.name);
}
}