blob: fbde0999c1a2c237f8bfcc09f9797e3bd9a0b50f [file] [log] [blame]
// Copyright (c) 2019, 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/protocol/protocol_generated.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
/// An object used to build the details for each token in the code being
/// analyzed.
class TokenDetailBuilder {
/// The list of details that were built.
List<TokenDetails> details = [];
/// Initialize a newly created builder.
TokenDetailBuilder();
/// Visit a [node] in the AST structure to build details for all of the tokens
/// contained by that node.
void visitNode(AstNode node) {
for (SyntacticEntity entity in node.childEntities) {
if (entity is Token) {
_createDetails(entity, null, null);
} else if (entity is SimpleIdentifier) {
String type = _getType(entity);
if (_isTypeName(entity)) {
type = 'dart:core;Type<$type>';
}
List<String> kinds = [];
if (entity.inDeclarationContext()) {
kinds.add('declaration');
} else {
kinds.add('reference');
}
_createDetails(entity.token, type, kinds);
} else if (entity is BooleanLiteral) {
_createDetails(entity.literal, _getType(entity), null);
} else if (entity is DoubleLiteral) {
_createDetails(entity.literal, _getType(entity), null);
} else if (entity is IntegerLiteral) {
_createDetails(entity.literal, _getType(entity), null);
} else if (entity is SimpleStringLiteral) {
_createDetails(entity.literal, _getType(entity), null);
} else if (entity is Comment) {
// Ignore comments and the references within them.
} else if (entity is AstNode) {
visitNode(entity);
}
}
}
/// Create the details for a single [token], using the given list of [kinds].
void _createDetails(Token token, String type, List<String> kinds) {
details.add(new TokenDetails(token.lexeme, token.offset,
type: type, validElementKinds: kinds));
}
/// Return a unique identifier for the type of the given [expression].
String _getType(Expression expression) {
StringBuffer buffer = new StringBuffer();
_writeType(buffer, expression.staticType);
return buffer.toString();
}
/// Return `true` if the [identifier] represents the name of a type.
bool _isTypeName(SimpleIdentifier identifier) {
AstNode parent = identifier.parent;
if (parent is TypeName && identifier == parent.name) {
return true;
} else if (parent is PrefixedIdentifier &&
parent.identifier == identifier) {
AstNode parent2 = parent.parent;
if (parent2 is TypeName && parent == parent2.name) {
return true;
}
}
return false;
}
/// Return a unique identifier for the type of the given [expression].
void _writeType(StringBuffer buffer, DartType type) {
if (type == null) {
// This should never happen if the AST has been resolved.
buffer.write('dynamic');
} else if (type is FunctionType) {
_writeType(buffer, type.returnType);
buffer.write(' Function(');
bool first = true;
for (var parameter in type.parameters) {
if (first) {
first = false;
} else {
buffer.write(', ');
}
_writeType(buffer, parameter.type);
}
buffer.write(')');
} else if (type is InterfaceType) {
Element element = type.element;
if (element == null || element.isSynthetic) {
buffer.write(type.displayName);
} else {
// String uri = element.library.source.uri.toString();
String name = element.name;
if (element is ClassMemberElement) {
String className = element.enclosingElement.name;
// TODO(brianwilkerson) Figure out why the uri is a file: URI when it
// ought to be a package: URI and restore the code below to include
// the URI in the string.
// buffer.write('$uri;$className;$name');
buffer.write('$className;$name');
} else {
// buffer.write('$uri;$name');
buffer.write('$name');
}
}
} else {
// Handle `void` and `dynamic`.
buffer.write(type.displayName);
}
}
}