c214c43758
Change-Id: I1bc7f5302797374609752ea5293b96c30719b41f Reviewed-on: https://dart-review.googlesource.com/51101 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
373 lines
14 KiB
Dart
373 lines
14 KiB
Dart
// 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' as analyzer;
|
|
import 'package:analyzer/dart/element/type.dart' as analyzer;
|
|
import 'package:analyzer/error/error.dart' as analyzer;
|
|
import 'package:analyzer/exception/exception.dart' as analyzer;
|
|
import 'package:analyzer/source/error_processor.dart' as analyzer;
|
|
import 'package:analyzer/source/line_info.dart' as analyzer;
|
|
import 'package:analyzer/src/generated/engine.dart' as analyzer;
|
|
import 'package:analyzer/src/generated/source.dart' as analyzer;
|
|
import 'package:analyzer/src/generated/utilities_dart.dart' as analyzer;
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
|
|
import 'package:analyzer_plugin/protocol/protocol_constants.dart' as plugin;
|
|
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
|
|
|
|
/**
|
|
* An object used to convert between objects defined by the 'analyzer' package
|
|
* and those defined by the plugin protocol.
|
|
*
|
|
* Clients may not extend, implement or mix-in this class.
|
|
*/
|
|
class AnalyzerConverter {
|
|
/**
|
|
* Convert the analysis [error] from the 'analyzer' package to an analysis
|
|
* error defined by the plugin API. If a [lineInfo] is provided then the
|
|
* error's location will have a start line and start column. If a [severity]
|
|
* is provided, then it will override the severity defined by the error.
|
|
*/
|
|
plugin.AnalysisError convertAnalysisError(analyzer.AnalysisError error,
|
|
{analyzer.LineInfo lineInfo, analyzer.ErrorSeverity severity}) {
|
|
analyzer.ErrorCode errorCode = error.errorCode;
|
|
severity ??= errorCode.errorSeverity;
|
|
int offset = error.offset;
|
|
int startLine = -1;
|
|
int startColumn = -1;
|
|
if (lineInfo != null) {
|
|
analyzer.CharacterLocation lineLocation = lineInfo.getLocation(offset);
|
|
if (lineLocation != null) {
|
|
startLine = lineLocation.lineNumber;
|
|
startColumn = lineLocation.columnNumber;
|
|
}
|
|
}
|
|
return new plugin.AnalysisError(
|
|
convertErrorSeverity(severity),
|
|
convertErrorType(errorCode.type),
|
|
new plugin.Location(error.source.fullName, offset, error.length,
|
|
startLine, startColumn),
|
|
error.message,
|
|
errorCode.name.toLowerCase(),
|
|
correction: error.correction,
|
|
hasFix: true);
|
|
}
|
|
|
|
/**
|
|
* Convert the list of analysis [errors] from the 'analyzer' package to a list
|
|
* of analysis errors defined by the plugin API. If a [lineInfo] is provided
|
|
* then the resulting errors locations will have a start line and start column.
|
|
* If an analysis [options] is provided then the severities of the errors will
|
|
* be altered based on those options.
|
|
*/
|
|
List<plugin.AnalysisError> convertAnalysisErrors(
|
|
List<analyzer.AnalysisError> errors,
|
|
{analyzer.LineInfo lineInfo,
|
|
analyzer.AnalysisOptions options}) {
|
|
List<plugin.AnalysisError> serverErrors = <plugin.AnalysisError>[];
|
|
for (analyzer.AnalysisError error in errors) {
|
|
analyzer.ErrorProcessor processor =
|
|
analyzer.ErrorProcessor.getProcessor(options, error);
|
|
if (processor != null) {
|
|
analyzer.ErrorSeverity severity = processor.severity;
|
|
// Errors with null severity are filtered out.
|
|
if (severity != null) {
|
|
// Specified severities override.
|
|
serverErrors.add(convertAnalysisError(error,
|
|
lineInfo: lineInfo, severity: severity));
|
|
}
|
|
} else {
|
|
serverErrors.add(convertAnalysisError(error, lineInfo: lineInfo));
|
|
}
|
|
}
|
|
return serverErrors;
|
|
}
|
|
|
|
/**
|
|
* Convert the given [element] from the 'analyzer' package to an element
|
|
* defined by the plugin API.
|
|
*/
|
|
plugin.Element convertElement(analyzer.Element element) {
|
|
plugin.ElementKind kind = _convertElementToElementKind(element);
|
|
return new plugin.Element(
|
|
kind,
|
|
element.displayName,
|
|
plugin.Element.makeFlags(
|
|
isPrivate: element.isPrivate,
|
|
isDeprecated: element.hasDeprecated,
|
|
isAbstract: _isAbstract(element),
|
|
isConst: _isConst(element),
|
|
isFinal: _isFinal(element),
|
|
isStatic: _isStatic(element)),
|
|
location: _locationFromElement(element),
|
|
typeParameters: _getTypeParametersString(element),
|
|
parameters: _getParametersString(element),
|
|
returnType: _getReturnTypeString(element));
|
|
}
|
|
|
|
/**
|
|
* Convert the element [kind] from the 'analyzer' package to an element kind
|
|
* defined by the plugin API.
|
|
*
|
|
* This method does not take into account that an instance of [ClassElement]
|
|
* can be an enum and an instance of [FieldElement] can be an enum constant.
|
|
* Use [_convertElementToElementKind] where possible.
|
|
*/
|
|
plugin.ElementKind convertElementKind(analyzer.ElementKind kind) {
|
|
if (kind == analyzer.ElementKind.CLASS) {
|
|
return plugin.ElementKind.CLASS;
|
|
} else if (kind == analyzer.ElementKind.COMPILATION_UNIT) {
|
|
return plugin.ElementKind.COMPILATION_UNIT;
|
|
} else if (kind == analyzer.ElementKind.CONSTRUCTOR) {
|
|
return plugin.ElementKind.CONSTRUCTOR;
|
|
} else if (kind == analyzer.ElementKind.FIELD) {
|
|
return plugin.ElementKind.FIELD;
|
|
} else if (kind == analyzer.ElementKind.FUNCTION) {
|
|
return plugin.ElementKind.FUNCTION;
|
|
} else if (kind == analyzer.ElementKind.FUNCTION_TYPE_ALIAS) {
|
|
return plugin.ElementKind.FUNCTION_TYPE_ALIAS;
|
|
} else if (kind == analyzer.ElementKind.GETTER) {
|
|
return plugin.ElementKind.GETTER;
|
|
} else if (kind == analyzer.ElementKind.LABEL) {
|
|
return plugin.ElementKind.LABEL;
|
|
} else if (kind == analyzer.ElementKind.LIBRARY) {
|
|
return plugin.ElementKind.LIBRARY;
|
|
} else if (kind == analyzer.ElementKind.LOCAL_VARIABLE) {
|
|
return plugin.ElementKind.LOCAL_VARIABLE;
|
|
} else if (kind == analyzer.ElementKind.METHOD) {
|
|
return plugin.ElementKind.METHOD;
|
|
} else if (kind == analyzer.ElementKind.PARAMETER) {
|
|
return plugin.ElementKind.PARAMETER;
|
|
} else if (kind == analyzer.ElementKind.PREFIX) {
|
|
return plugin.ElementKind.PREFIX;
|
|
} else if (kind == analyzer.ElementKind.SETTER) {
|
|
return plugin.ElementKind.SETTER;
|
|
} else if (kind == analyzer.ElementKind.TOP_LEVEL_VARIABLE) {
|
|
return plugin.ElementKind.TOP_LEVEL_VARIABLE;
|
|
} else if (kind == analyzer.ElementKind.TYPE_PARAMETER) {
|
|
return plugin.ElementKind.TYPE_PARAMETER;
|
|
}
|
|
return plugin.ElementKind.UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* Convert the error [severity] from the 'analyzer' package to an analysis
|
|
* error severity defined by the plugin API.
|
|
*/
|
|
plugin.AnalysisErrorSeverity convertErrorSeverity(
|
|
analyzer.ErrorSeverity severity) =>
|
|
new plugin.AnalysisErrorSeverity(severity.name);
|
|
|
|
/**
|
|
*Convert the error [type] from the 'analyzer' package to an analysis error
|
|
* type defined by the plugin API.
|
|
*/
|
|
plugin.AnalysisErrorType convertErrorType(analyzer.ErrorType type) =>
|
|
new plugin.AnalysisErrorType(type.name);
|
|
|
|
/**
|
|
* Convert the element kind of the [element] from the 'analyzer' package to an
|
|
* element kind defined by the plugin API.
|
|
*/
|
|
plugin.ElementKind _convertElementToElementKind(analyzer.Element element) {
|
|
if (element is analyzer.ClassElement && element.isEnum) {
|
|
return plugin.ElementKind.ENUM;
|
|
} else if (element is analyzer.FieldElement &&
|
|
element.isEnumConstant &&
|
|
// MyEnum.values and MyEnum.one.index return isEnumConstant = true
|
|
// so these additional checks are necessary.
|
|
// TODO(danrubel) MyEnum.values is constant, but is a list
|
|
// so should it return isEnumConstant = true?
|
|
// MyEnum.one.index is final but *not* constant
|
|
// so should it return isEnumConstant = true?
|
|
// Or should we return ElementKind.ENUM_CONSTANT here
|
|
// in either or both of these cases?
|
|
element.type != null &&
|
|
element.type.element == element.enclosingElement) {
|
|
return plugin.ElementKind.ENUM_CONSTANT;
|
|
}
|
|
return convertElementKind(element.kind);
|
|
}
|
|
|
|
/**
|
|
* Return a textual representation of the parameters of the given [element],
|
|
* or `null` if the element does not have any parameters.
|
|
*/
|
|
String _getParametersString(analyzer.Element element) {
|
|
// TODO(scheglov) expose the corresponding feature from ExecutableElement
|
|
List<analyzer.ParameterElement> parameters;
|
|
if (element is analyzer.ExecutableElement) {
|
|
// valid getters don't have parameters
|
|
if (element.kind == analyzer.ElementKind.GETTER &&
|
|
element.parameters.isEmpty) {
|
|
return null;
|
|
}
|
|
parameters = element.parameters;
|
|
} else if (element is analyzer.FunctionTypeAliasElement) {
|
|
parameters = element.parameters;
|
|
} else {
|
|
return null;
|
|
}
|
|
StringBuffer buffer = new StringBuffer();
|
|
String closeOptionalString = '';
|
|
buffer.write('(');
|
|
for (int i = 0; i < parameters.length; i++) {
|
|
analyzer.ParameterElement parameter = parameters[i];
|
|
if (i > 0) {
|
|
buffer.write(', ');
|
|
}
|
|
if (closeOptionalString.isEmpty) {
|
|
if (parameter.isNamed) {
|
|
buffer.write('{');
|
|
closeOptionalString = '}';
|
|
} else if (parameter.isOptionalPositional) {
|
|
buffer.write('[');
|
|
closeOptionalString = ']';
|
|
}
|
|
}
|
|
parameter.appendToWithoutDelimiters(buffer);
|
|
}
|
|
buffer.write(closeOptionalString);
|
|
buffer.write(')');
|
|
return buffer.toString();
|
|
}
|
|
|
|
/**
|
|
* Return a textual representation of the return type of the given [element],
|
|
* or `null` if the element does not have a return type.
|
|
*/
|
|
String _getReturnTypeString(analyzer.Element element) {
|
|
if (element is analyzer.ExecutableElement) {
|
|
if (element.kind == analyzer.ElementKind.SETTER) {
|
|
return null;
|
|
}
|
|
return element.returnType?.toString();
|
|
} else if (element is analyzer.VariableElement) {
|
|
analyzer.DartType type = element.type;
|
|
return type != null ? type.displayName : 'dynamic';
|
|
} else if (element is analyzer.FunctionTypeAliasElement) {
|
|
return element.returnType.toString();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Return a textual representation of the type parameters of the given
|
|
* [element], or `null` if the element does not have type parameters.
|
|
*/
|
|
String _getTypeParametersString(analyzer.Element element) {
|
|
if (element is analyzer.TypeParameterizedElement) {
|
|
List<analyzer.TypeParameterElement> typeParameters =
|
|
element.typeParameters;
|
|
if (typeParameters == null || typeParameters.isEmpty) {
|
|
return null;
|
|
}
|
|
return '<${typeParameters.join(', ')}>';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Return the compilation unit containing the given [element].
|
|
*/
|
|
analyzer.CompilationUnitElement _getUnitElement(analyzer.Element element) {
|
|
if (element is analyzer.CompilationUnitElement) {
|
|
return element;
|
|
}
|
|
if (element?.enclosingElement is analyzer.LibraryElement) {
|
|
element = element.enclosingElement;
|
|
}
|
|
if (element is analyzer.LibraryElement) {
|
|
return element.definingCompilationUnit;
|
|
}
|
|
for (; element != null; element = element.enclosingElement) {
|
|
if (element is analyzer.CompilationUnitElement) {
|
|
return element;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool _isAbstract(analyzer.Element element) {
|
|
// TODO(scheglov) add isAbstract to Element API
|
|
if (element is analyzer.ClassElement) {
|
|
return element.isAbstract;
|
|
} else if (element is analyzer.MethodElement) {
|
|
return element.isAbstract;
|
|
} else if (element is analyzer.PropertyAccessorElement) {
|
|
return element.isAbstract;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool _isConst(analyzer.Element element) {
|
|
// TODO(scheglov) add isConst to Element API
|
|
if (element is analyzer.ConstructorElement) {
|
|
return element.isConst;
|
|
} else if (element is analyzer.VariableElement) {
|
|
return element.isConst;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool _isFinal(analyzer.Element element) {
|
|
// TODO(scheglov) add isFinal to Element API
|
|
if (element is analyzer.VariableElement) {
|
|
return element.isFinal;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool _isStatic(analyzer.Element element) {
|
|
// TODO(scheglov) add isStatic to Element API
|
|
if (element is analyzer.ExecutableElement) {
|
|
return element.isStatic;
|
|
} else if (element is analyzer.PropertyInducingElement) {
|
|
return element.isStatic;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create and return a location within the given [unitElement] at the given
|
|
* [range].
|
|
*/
|
|
plugin.Location _locationForArgs(
|
|
analyzer.CompilationUnitElement unitElement, analyzer.SourceRange range) {
|
|
int startLine = 0;
|
|
int startColumn = 0;
|
|
try {
|
|
analyzer.LineInfo lineInfo = unitElement.lineInfo;
|
|
if (lineInfo != null) {
|
|
analyzer.CharacterLocation offsetLocation =
|
|
lineInfo.getLocation(range.offset);
|
|
startLine = offsetLocation.lineNumber;
|
|
startColumn = offsetLocation.columnNumber;
|
|
}
|
|
} on analyzer.AnalysisException {
|
|
// Ignore exceptions
|
|
}
|
|
return new plugin.Location(unitElement.source.fullName, range.offset,
|
|
range.length, startLine, startColumn);
|
|
}
|
|
|
|
/**
|
|
* Create a location based on an the given [element].
|
|
*/
|
|
plugin.Location _locationFromElement(analyzer.Element element) {
|
|
if (element == null || element.source == null) {
|
|
return null;
|
|
}
|
|
int offset = element.nameOffset;
|
|
int length = element.nameLength;
|
|
if (element is analyzer.CompilationUnitElement ||
|
|
(element is analyzer.LibraryElement && offset < 0)) {
|
|
offset = 0;
|
|
length = 0;
|
|
}
|
|
analyzer.CompilationUnitElement unitElement = _getUnitElement(element);
|
|
analyzer.SourceRange range = new analyzer.SourceRange(offset, length);
|
|
return _locationForArgs(unitElement, range);
|
|
}
|
|
}
|