blob: 491f529ea6a1a6031c3ed84774d2eac9ccfcb43f [file] [log] [blame]
library angular2.src.analysis.analyzer_plugin.src.tasks;
import 'package:analyzer/dart/ast/ast.dart' as ast;
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/src/dart/ast/utilities.dart' as utils;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:angular_analyzer_plugin/tasks.dart';
import 'package:angular_analyzer_plugin/src/angular_html_parser.dart';
import 'package:html/dom.dart' as html;
import 'package:html/parser.dart' as html;
import 'package:source_span/source_span.dart';
class OffsettingConstantEvaluator extends utils.ConstantEvaluator {
bool offsetsAreValid = true;
Object value;
ast.AstNode lastUnoffsettableNode;
@override
Object visitAdjacentStrings(ast.AdjacentStrings node) {
StringBuffer buffer = new StringBuffer();
int lastEndingOffset = null;
for (ast.StringLiteral string in node.strings) {
Object value = string.accept(this);
if (identical(value, utils.ConstantEvaluator.NOT_A_CONSTANT)) {
return value;
}
// preserve offsets across the split by padding
if (lastEndingOffset != null) {
buffer.write(' ' * (string.offset - lastEndingOffset));
}
lastEndingOffset = string.offset + string.length;
buffer.write(value);
}
return buffer.toString();
}
@override
Object visitBinaryExpression(ast.BinaryExpression node) {
if (node.operator.type == TokenType.PLUS) {
Object leftOperand = node.leftOperand.accept(this);
if (identical(leftOperand, utils.ConstantEvaluator.NOT_A_CONSTANT)) {
return leftOperand;
}
Object rightOperand = node.rightOperand.accept(this);
if (identical(rightOperand, utils.ConstantEvaluator.NOT_A_CONSTANT)) {
return rightOperand;
}
// numeric or {@code null}
if (leftOperand is String && rightOperand is String) {
int gap = node.rightOperand.offset -
node.leftOperand.offset -
node.leftOperand.length;
return leftOperand + (' ' * gap) + rightOperand;
}
}
return super.visitBinaryExpression(node);
}
@override
Object visitStringInterpolation(ast.StringInterpolation node) {
offsetsAreValid = false;
lastUnoffsettableNode = node;
return super.visitStringInterpolation(node);
}
@override
Object visitMethodInvocation(ast.MethodInvocation node) {
offsetsAreValid = false;
lastUnoffsettableNode = node;
return super.visitMethodInvocation(node);
}
@override
Object visitParenthesizedExpression(ast.ParenthesizedExpression node) {
offsetsAreValid = false;
lastUnoffsettableNode = node;
int preGap = node.expression.offset - node.offset;
int postGap = node.offset +
node.length -
node.expression.offset -
node.expression.length;
Object value = super.visitParenthesizedExpression(node);
if (value is String) {
return ' ' * preGap + value + ' ' * postGap;
}
return value;
}
@override
Object visitSimpleStringLiteral(ast.SimpleStringLiteral node) {
int gap = node.contentsOffset - node.offset;
lastUnoffsettableNode = node;
return ' ' * gap + node.value + ' ';
}
@override
Object visitPrefixedIdentifier(ast.PrefixedIdentifier node) {
offsetsAreValid = false;
lastUnoffsettableNode = node;
return super.visitPrefixedIdentifier(node);
}
@override
Object visitPropertyAccess(ast.PropertyAccess node) {
offsetsAreValid = false;
lastUnoffsettableNode = node;
return super.visitPropertyAccess(node);
}
@override
Object visitSimpleIdentifier(ast.SimpleIdentifier node) {
offsetsAreValid = false;
lastUnoffsettableNode = node;
return super.visitSimpleIdentifier(node);
}
}
/**
* Helper for processing Angular annotations.
*/
class AnnotationProcessorMixin {
RecordingErrorListener errorListener = new RecordingErrorListener();
ErrorReporter errorReporter;
/**
* The evaluator of constant values, such as annotation arguments.
*/
final utils.ConstantEvaluator _constantEvaluator =
new utils.ConstantEvaluator();
/**
* Initialize the processor working in the given [target].
*/
void initAnnotationProcessor(Source source) {
assert(errorReporter == null);
errorReporter = new ErrorReporter(errorListener, source);
}
/**
* Returns the [String] value of the given [expression].
* If [expression] does not have a [String] value, reports an error
* and returns `null`.
*/
String getExpressionString(ast.Expression expression) {
if (expression != null) {
Object value = expression.accept(_constantEvaluator);
if (value is String) {
return value;
}
errorReporter.reportErrorForNode(
AngularWarningCode.STRING_VALUE_EXPECTED, expression);
}
return null;
}
/**
* Returns the [String] value of the given [expression].
* If [expression] does not have a [String] value, reports an error
* and returns `null`.
*/
OffsettingConstantEvaluator calculateStringWithOffsets(
ast.Expression expression) {
if (expression != null) {
OffsettingConstantEvaluator evaluator = new OffsettingConstantEvaluator();
evaluator.value = expression.accept(evaluator);
if (evaluator.value is String) {
if (!evaluator.offsetsAreValid) {
errorReporter.reportErrorForNode(
AngularWarningCode.OFFSETS_CANNOT_BE_CREATED,
evaluator.lastUnoffsettableNode);
}
return evaluator;
}
errorReporter.reportErrorForNode(
AngularWarningCode.STRING_VALUE_EXPECTED, expression);
}
return null;
}
/**
* Returns the value of the argument with the given [name].
* Returns `null` if not found.
*/
ast.Expression getNamedArgument(ast.Annotation node, String name) {
if (node.arguments != null) {
List<ast.Expression> arguments = node.arguments.arguments;
for (ast.Expression argument in arguments) {
if (argument is ast.NamedExpression &&
argument.name != null &&
argument.name.label != null &&
argument.name.label.name == name) {
return argument.expression;
}
}
}
return null;
}
/**
* Returns `true` is the given [node] is resolved to a creation of an Angular
* annotation class with the given [name].
*/
bool isAngularAnnotation(ast.Annotation node, String name) {
if (node.element is ConstructorElement) {
ClassElement clazz = node.element.enclosingElement;
return clazz.library.source.uri.path
.endsWith('angular2/src/core/metadata.dart') &&
clazz.name == name;
}
return false;
}
}
List<AnalysisError> filterParserErrors(
AngularHtmlParser parser, String content, Source source) {
List<AnalysisError> errors = <AnalysisError>[];
List<html.ParseError> parseErrors = parser.errors;
for (html.ParseError parseError in parseErrors) {
//Append error codes that are useful to this analyzer
if (parseError.errorCode == 'eof-in-tag-name') {
SourceSpan span = parseError.span;
errors.add(new AnalysisError(
source,
span.start.offset,
span.length,
HtmlErrorCode.PARSE_ERROR,
[parseError.errorCode, content.substring(span.start.offset)]));
}
}
return errors;
}