Tim Blasi 9ae171e0c8 refactor(dart/transform): Improve logging
Ensure that, where possible, log messages include an [AssetId].

Closes #5369
2015-11-24 18:41:24 +00:00

346 lines
12 KiB
Dart

library angular2.transform.common.code.reflection_info_code;
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/annotation_matcher.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/model/reflection_info_model.pb.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/property_utils.dart';
import 'package:barback/barback.dart' show AssetId;
import 'annotation_code.dart';
import 'parameter_code.dart';
/// Visitor responsible for parsing [ClassDeclaration]s into
/// [ReflectionInfoModel]s.
class ReflectionInfoVisitor extends RecursiveAstVisitor<ReflectionInfoModel> {
/// The file we are processing.
final AssetId assetId;
/// Responsible for testing whether [Annotation]s are those recognized by
/// Angular 2, for example `@Component`.
final AnnotationMatcher _annotationMatcher;
final AnnotationVisitor _annotationVisitor;
final ParameterVisitor _parameterVisitor = new ParameterVisitor();
final _PropertyMetadataVisitor _propMetadataVisitor;
ReflectionInfoVisitor._(this.assetId, this._annotationMatcher,
this._annotationVisitor, this._propMetadataVisitor);
factory ReflectionInfoVisitor(
AssetId assetId, AnnotationMatcher annotationMatcher) {
var annotationVisitor = new AnnotationVisitor(assetId, annotationMatcher);
return new ReflectionInfoVisitor._(assetId, annotationMatcher,
annotationVisitor, new _PropertyMetadataVisitor(annotationVisitor));
}
ConstructorDeclaration _getCtor(ClassDeclaration node) {
int numCtorsFound = 0;
var ctor = null;
for (ClassMember classMember in node.members) {
if (classMember is ConstructorDeclaration) {
numCtorsFound++;
ConstructorDeclaration constructor = classMember;
// Use the unnnamed constructor if it is present.
// Otherwise, use the first encountered.
if (ctor == null) {
ctor = constructor;
} else if (constructor.name == null) {
ctor = constructor;
}
}
}
if (numCtorsFound > 1) {
var ctorName = ctor.name;
if (ctorName != null) {
log.warning(
'Found ${numCtorsFound} constructors for class '
'${node.name}; using constructor ${ctorName}.',
asset: assetId);
}
}
return ctor;
}
@override
ReflectionInfoModel visitClassDeclaration(ClassDeclaration node) {
if (!node.metadata
.any((a) => _annotationMatcher.hasMatch(a.name, assetId))) {
return null;
}
var ctor = _getCtor(node);
var model = new ReflectionInfoModel()..name = '${node.name}';
if (ctor != null && ctor.name != null) {
model.ctorName = '${ctor.name}';
}
if (node.metadata != null) {
var componentDirectives, viewDirectives;
node.metadata.forEach((node) {
if (_annotationMatcher.isComponent(node, assetId)) {
componentDirectives = _extractDirectives(node);
} else if (_annotationMatcher.isView(node, assetId)) {
viewDirectives = _extractDirectives(node);
}
model.annotations.add(_annotationVisitor.visitAnnotation(node));
});
if (componentDirectives != null && componentDirectives.isNotEmpty) {
if (viewDirectives != null) {
log.warning(
'Cannot specify view parameters on @Component when a @View '
'is present. Component name: ${model.name}',
asset: assetId);
}
model.directives.addAll(componentDirectives);
} else if (viewDirectives != null) {
model.directives.addAll(viewDirectives);
}
}
if (ctor != null &&
ctor.parameters != null &&
ctor.parameters.parameters != null) {
ctor.parameters.parameters.forEach((node) {
model.parameters.add(node.accept(_parameterVisitor));
});
}
if (node.implementsClause != null &&
node.implementsClause.interfaces != null &&
node.implementsClause.interfaces.isNotEmpty) {
model.interfaces.addAll(node.implementsClause.interfaces
.map((interface) => '${interface.name}'));
}
// Record annotations attached to properties.
for (var member in node.members) {
var propMetaList = member.accept(_propMetadataVisitor);
if (propMetaList != null) {
model.propertyMetadata.addAll(propMetaList);
}
}
_coalesce(model.propertyMetadata);
return model;
}
// If a class has a getter & a setter with the same name and each has
// individual metadata, collapse to a single entry.
void _coalesce(List<PropertyMetadataModel> propertyMetadata) {
if (propertyMetadata.isEmpty) return;
var firstSeenIdxMap = <String, int>{};
firstSeenIdxMap[propertyMetadata[0].name] = 0;
var i = 1;
while (i < propertyMetadata.length) {
var propName = propertyMetadata[i].name;
if (firstSeenIdxMap.containsKey(propName)) {
var propNameIdx = firstSeenIdxMap[propName];
// We have seen this name before, combine the metadata lists.
propertyMetadata[propNameIdx]
.annotations
.addAll(propertyMetadata[i].annotations);
// Remove the higher index, okay since we directly check `length` above.
propertyMetadata.removeAt(i);
} else {
firstSeenIdxMap[propName] = i;
++i;
}
}
}
/// Returns [PrefixedDirective] values parsed from the value of the
/// `directives` parameter of the provided `node`.
/// This will always return a non-null value, so if there are no `directives`
/// specified on `node`, it will return an empty iterable.
Iterable<PrefixedDirective> _extractDirectives(Annotation node) {
assert(_annotationMatcher.isComponent(node, assetId) ||
_annotationMatcher.isView(node, assetId));
if (node.arguments == null && node.arguments.arguments == null) {
return const [];
}
final directivesNode = node.arguments.arguments.firstWhere((arg) {
return arg is NamedExpression && '${arg.name.label}' == 'directives';
}, orElse: () => null);
if (directivesNode == null) return const [];
if (directivesNode.expression is! ListLiteral) {
log.warning(
'Angular 2 expects a list literal for `directives` '
'but found a ${directivesNode.expression.runtimeType}',
asset: assetId);
return const [];
}
final directives = <PrefixedDirective>[];
for (var dep in (directivesNode.expression as ListLiteral).elements) {
if (dep is PrefixedIdentifier) {
directives.add(new PrefixedDirective()
..prefix = '${dep.prefix}'
..name = '${dep.identifier}');
} else if (dep is Identifier) {
directives.add(new PrefixedDirective()..name = '${dep}');
} else {
log.warning('Found unexpected value $dep in `directives`.',
asset: assetId);
}
}
return directives;
}
@override
ReflectionInfoModel visitFunctionDeclaration(FunctionDeclaration node) {
if (!node.metadata
.any((a) => _annotationMatcher.hasMatch(a.name, assetId))) {
return null;
}
var model = new ReflectionInfoModel()
..name = '${node.name}'
..isFunction = true;
if (node.metadata != null) {
node.metadata.forEach((node) {
var annotation = _annotationVisitor.visitAnnotation(node);
if (annotation != null) {
model.annotations.add(annotation);
}
});
}
if (node.functionExpression.parameters != null &&
node.functionExpression.parameters.parameters != null) {
node.functionExpression.parameters.parameters.forEach((node) {
var param = node.accept(_parameterVisitor);
if (param != null) {
model.parameters.add(param);
}
});
}
return model;
}
}
/// Visitor responsible for parsing [ClassMember]s into
/// [PropertyMetadataModel]s.
class _PropertyMetadataVisitor
extends SimpleAstVisitor<List<PropertyMetadataModel>> {
final AnnotationVisitor _annotationVisitor;
_PropertyMetadataVisitor(this._annotationVisitor);
@override
List<PropertyMetadataModel> visitFieldDeclaration(FieldDeclaration node) {
var retVal = null;
for (var variable in node.fields.variables) {
var propModel = null;
for (var meta in node.metadata) {
var annotationModel = meta.accept(_annotationVisitor);
if (annotationModel != null) {
if (propModel == null) {
propModel = new PropertyMetadataModel()..name = '${variable.name}';
}
propModel.annotations.add(annotationModel);
}
}
if (propModel != null && propModel.annotations.isNotEmpty) {
if (retVal == null) {
retVal = <PropertyMetadataModel>[];
}
retVal.add(propModel);
}
}
return retVal;
}
@override
List<PropertyMetadataModel> visitMethodDeclaration(MethodDeclaration node) {
if (node.isGetter || node.isSetter) {
var propModel = null;
for (var meta in node.metadata) {
var annotationModel = meta.accept(_annotationVisitor);
if (annotationModel != null) {
if (propModel == null) {
propModel = new PropertyMetadataModel()..name = '${node.name}';
}
propModel.annotations.add(annotationModel);
}
}
if (propModel != null && propModel.annotations.isNotEmpty) {
return <PropertyMetadataModel>[propModel];
}
}
return null;
}
}
/// Defines the format in which an [ReflectionInfoModel] is expressed as Dart
/// code in a `.ng_deps.dart` file.
abstract class ReflectionWriterMixin
implements AnnotationWriterMixin, ParameterWriterMixin {
StringBuffer get buffer;
void _writeListWithSeparator(List l, Function writeFn,
{String prefix, String suffix, String separator: ', '}) {
buffer.write(prefix);
for (var i = 0, iLen = l.length; i < iLen; ++i) {
if (i != 0) {
buffer.write(', ');
}
writeFn(l[i]);
}
buffer.write(suffix);
}
void writeRegistration(ReflectionInfoModel model) {
buffer.write('..register');
if (model.isFunction) {
buffer.write('Function');
} else {
buffer.write('Type');
}
buffer.writeln('(${model.name}, new $REFLECTOR_PREFIX.ReflectionInfo(');
// Annotations
_writeListWithSeparator(model.annotations, writeAnnotationModel,
prefix: 'const [', suffix: ']');
// Parameters
_writeListWithSeparator(model.parameters, writeParameterModelForList,
prefix: ',\nconst [', suffix: ']');
if (!model.isFunction) {
// Factory
_writeListWithSeparator(
model.parameters, writeParameterModelForDeclaration,
prefix: ',\n(', suffix: ')');
buffer.write(' => new ${model.name}');
if (model.ctorName != null && model.ctorName.isNotEmpty) {
buffer.write('.${model.ctorName}');
}
_writeListWithSeparator(model.parameters, writeParameterModelForImpl,
prefix: '(', suffix: ')');
// Interfaces
var hasPropertyMetadata =
model.propertyMetadata != null && model.propertyMetadata.isNotEmpty;
if (model.interfaces != null && model.interfaces.isNotEmpty) {
_writeListWithSeparator(model.interfaces, buffer.write,
prefix: ',\nconst [', suffix: ']');
} else if (hasPropertyMetadata) {
buffer.write(',\nconst []');
}
// Property Metadata
if (hasPropertyMetadata) {
buffer.write(',\nconst {');
for (var propMeta in model.propertyMetadata) {
if (propMeta != model.propertyMetadata.first) {
buffer.write(', ');
}
_writeListWithSeparator(propMeta.annotations, writeAnnotationModel,
prefix: "\n'${sanitize(propMeta.name)}': const [", suffix: ']');
}
buffer.write('}');
}
}
buffer.writeln(')\n)');
}
}