feat(dart/transform): Populate `lifecycle` from lifecycle interfaces
When a `Directive` implements a lifecycle interface (e.g. `OnChange` or `OnInit`), populate its `lifecycle` property if not already populated). Closes #3181
This commit is contained in:
parent
854b5b7da8
commit
8ad4ad57d1
|
@ -2,176 +2,97 @@ library angular2.transform.common.annotation_matcher;
|
|||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:barback/barback.dart' show AssetId;
|
||||
import 'package:code_transformers/assets.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'logging.dart' show logger;
|
||||
import 'class_matcher_base.dart';
|
||||
|
||||
/// [AnnotationDescriptor]s for the default angular annotations that can appear
|
||||
export 'class_matcher_base.dart' show ClassDescriptor;
|
||||
|
||||
/// [ClassDescriptor]s for the default angular annotations that can appear
|
||||
/// on a class. These classes are re-exported in many places so this covers all
|
||||
/// the possible libraries which could provide them.
|
||||
const INJECTABLES = const [
|
||||
const AnnotationDescriptor(
|
||||
'Injectable', 'package:angular2/src/di/decorators.dart', null),
|
||||
const AnnotationDescriptor('Injectable', 'package:angular2/di.dart', null),
|
||||
const AnnotationDescriptor(
|
||||
'Injectable', 'package:angular2/angular2.dart', null),
|
||||
const ClassDescriptor(
|
||||
'Injectable', 'package:angular2/src/di/decorators.dart'),
|
||||
const ClassDescriptor('Injectable', 'package:angular2/di.dart'),
|
||||
const ClassDescriptor('Injectable', 'package:angular2/angular2.dart'),
|
||||
];
|
||||
|
||||
const DIRECTIVES = const [
|
||||
const AnnotationDescriptor('Directive',
|
||||
'package:angular2/src/core/annotations/annotations.dart', 'Injectable'),
|
||||
const AnnotationDescriptor('Directive',
|
||||
'package:angular2/src/core/annotations/decorators.dart', 'Injectable'),
|
||||
const AnnotationDescriptor('Directive',
|
||||
const ClassDescriptor(
|
||||
'Directive', 'package:angular2/src/core/annotations/annotations.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor(
|
||||
'Directive', 'package:angular2/src/core/annotations/decorators.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive',
|
||||
'package:angular2/src/core/annotations_impl/annotations.dart',
|
||||
'Injectable'),
|
||||
const AnnotationDescriptor(
|
||||
'Directive', 'package:angular2/annotations.dart', 'Injectable'),
|
||||
const AnnotationDescriptor(
|
||||
'Directive', 'package:angular2/angular2.dart', 'Injectable'),
|
||||
const AnnotationDescriptor(
|
||||
'Directive', 'package:angular2/core.dart', 'Injectable'),
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/annotations.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/angular2.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/core.dart',
|
||||
superClass: 'Injectable'),
|
||||
];
|
||||
|
||||
const COMPONENTS = const [
|
||||
const AnnotationDescriptor('Component',
|
||||
'package:angular2/src/core/annotations/annotations.dart', 'Directive'),
|
||||
const AnnotationDescriptor('Component',
|
||||
'package:angular2/src/core/annotations/decorators.dart', 'Directive'),
|
||||
const AnnotationDescriptor('Component',
|
||||
const ClassDescriptor(
|
||||
'Component', 'package:angular2/src/core/annotations/annotations.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor(
|
||||
'Component', 'package:angular2/src/core/annotations/decorators.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component',
|
||||
'package:angular2/src/core/annotations_impl/annotations.dart',
|
||||
'Directive'),
|
||||
const AnnotationDescriptor(
|
||||
'Component', 'package:angular2/annotations.dart', 'Directive'),
|
||||
const AnnotationDescriptor(
|
||||
'Component', 'package:angular2/angular2.dart', 'Directive'),
|
||||
const AnnotationDescriptor(
|
||||
'Component', 'package:angular2/core.dart', 'Directive'),
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/annotations.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/angular2.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/core.dart',
|
||||
superClass: 'Directive'),
|
||||
];
|
||||
|
||||
const VIEWS = const [
|
||||
const AnnotationDescriptor('View', 'package:angular2/view.dart', null),
|
||||
const AnnotationDescriptor('View', 'package:angular2/angular2.dart', null),
|
||||
const AnnotationDescriptor('View', 'package:angular2/core.dart', null),
|
||||
const AnnotationDescriptor(
|
||||
'View', 'package:angular2/src/core/annotations/view.dart', null),
|
||||
const AnnotationDescriptor(
|
||||
'View', 'package:angular2/src/core/annotations_impl/view.dart', null),
|
||||
const ClassDescriptor('View', 'package:angular2/view.dart'),
|
||||
const ClassDescriptor('View', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor('View', 'package:angular2/core.dart'),
|
||||
const ClassDescriptor(
|
||||
'View', 'package:angular2/src/core/annotations/view.dart'),
|
||||
const ClassDescriptor(
|
||||
'View', 'package:angular2/src/core/annotations_impl/view.dart'),
|
||||
];
|
||||
|
||||
/// Checks if a given [Annotation] matches any of the given
|
||||
/// [AnnotationDescriptors].
|
||||
class AnnotationMatcher {
|
||||
/// Always start out with the default angular [AnnotationDescriptor]s.
|
||||
final List<AnnotationDescriptor> _annotations = []
|
||||
..addAll(VIEWS)
|
||||
..addAll(COMPONENTS)
|
||||
..addAll(INJECTABLES)
|
||||
..addAll(DIRECTIVES);
|
||||
/// [ClassDescriptors].
|
||||
class AnnotationMatcher extends ClassMatcherBase {
|
||||
AnnotationMatcher._(classDescriptors) : super(classDescriptors);
|
||||
|
||||
AnnotationMatcher();
|
||||
factory AnnotationMatcher() {
|
||||
return new AnnotationMatcher._([]
|
||||
..addAll(COMPONENTS)
|
||||
..addAll(DIRECTIVES)
|
||||
..addAll(INJECTABLES)
|
||||
..addAll(VIEWS));
|
||||
}
|
||||
|
||||
/// Adds a new [AnnotationDescriptor].
|
||||
void add(AnnotationDescriptor annotation) => _annotations.add(annotation);
|
||||
|
||||
/// Adds a number of [AnnotationDescriptor]s.
|
||||
void addAll(Iterable<AnnotationDescriptor> annotations) =>
|
||||
_annotations.addAll(annotations);
|
||||
|
||||
/// Returns the first [AnnotationDescriptor] that matches the given
|
||||
/// [Annotation] node which appears in `assetId`.
|
||||
AnnotationDescriptor firstMatch(Annotation annotation, AssetId assetId) =>
|
||||
_annotations.firstWhere((a) => _matchAnnotation(annotation, a, assetId),
|
||||
orElse: () => null);
|
||||
|
||||
/// Checks whether an [Annotation] node matches any [AnnotationDescriptor].
|
||||
bool hasMatch(Annotation annotation, AssetId assetId) =>
|
||||
_annotations.any((a) => _matchAnnotation(annotation, a, assetId));
|
||||
bool _implementsWithWarning(
|
||||
ClassDescriptor descriptor, List<ClassDescriptor> interfaces) =>
|
||||
implements(descriptor, interfaces,
|
||||
missingSuperClassWarning: 'Missing `custom_annotation` entry for `${descriptor.superClass}`.');
|
||||
|
||||
/// Checks if an [Annotation] node implements [Injectable].
|
||||
bool isInjectable(Annotation annotation, AssetId assetId) =>
|
||||
_implements(firstMatch(annotation, assetId), INJECTABLES);
|
||||
_implementsWithWarning(firstMatch(annotation.name, assetId), INJECTABLES);
|
||||
|
||||
/// Checks if an [Annotation] node implements [Directive].
|
||||
bool isDirective(Annotation annotation, AssetId assetId) =>
|
||||
_implements(firstMatch(annotation, assetId), DIRECTIVES);
|
||||
_implementsWithWarning(firstMatch(annotation.name, assetId), DIRECTIVES);
|
||||
|
||||
/// Checks if an [Annotation] node implements [Component].
|
||||
bool isComponent(Annotation annotation, AssetId assetId) =>
|
||||
_implements(firstMatch(annotation, assetId), COMPONENTS);
|
||||
_implementsWithWarning(firstMatch(annotation.name, assetId), COMPONENTS);
|
||||
|
||||
/// Checks if an [Annotation] node implements [View].
|
||||
bool isView(Annotation annotation, AssetId assetId) =>
|
||||
_implements(firstMatch(annotation, assetId), VIEWS);
|
||||
|
||||
/// Checks if `descriptor` extends or is any of the supplied `interfaces`.
|
||||
bool _implements(
|
||||
AnnotationDescriptor descriptor, List<AnnotationDescriptor> interfaces) {
|
||||
if (descriptor == null) return false;
|
||||
if (interfaces.contains(descriptor)) return true;
|
||||
if (descriptor.superClass == null) return false;
|
||||
var superClass = _annotations.firstWhere(
|
||||
(a) => a.name == descriptor.superClass, orElse: () => null);
|
||||
if (superClass == null) {
|
||||
logger.warning(
|
||||
'Missing `custom_annotation` entry for `${descriptor.superClass}`.');
|
||||
return false;
|
||||
}
|
||||
return _implements(superClass, interfaces);
|
||||
}
|
||||
|
||||
// Checks if an [Annotation] matches an [AnnotationDescriptor].
|
||||
static bool _matchAnnotation(
|
||||
Annotation annotation, AnnotationDescriptor descriptor, AssetId assetId) {
|
||||
String name;
|
||||
Identifier prefix;
|
||||
if (annotation.name is PrefixedIdentifier) {
|
||||
// TODO(jakemac): Shouldn't really need a cast here, remove once
|
||||
// https://github.com/dart-lang/sdk/issues/23798 is fixed.
|
||||
var prefixedName = annotation.name as PrefixedIdentifier;
|
||||
name = prefixedName.identifier.name;
|
||||
prefix = prefixedName.prefix;
|
||||
} else {
|
||||
name = annotation.name.name;
|
||||
}
|
||||
if (name != descriptor.name) return false;
|
||||
return (annotation.root as CompilationUnit).directives
|
||||
.where((d) => d is ImportDirective)
|
||||
.any((ImportDirective i) {
|
||||
var importMatch = false;
|
||||
var uriString = i.uri.stringValue;
|
||||
if (uriString == descriptor.import) {
|
||||
importMatch = true;
|
||||
} else if (uriString.startsWith('package:') ||
|
||||
uriString.startsWith('dart:')) {
|
||||
return false;
|
||||
} else {
|
||||
importMatch = descriptor.assetId ==
|
||||
uriToAssetId(assetId, uriString, logger, null);
|
||||
}
|
||||
|
||||
if (!importMatch) return false;
|
||||
if (prefix == null) return i.prefix == null;
|
||||
if (i.prefix == null) return false;
|
||||
return prefix.name == i.prefix.name;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// String based description of an annotation class and its location.
|
||||
class AnnotationDescriptor {
|
||||
/// The name of the class.
|
||||
final String name;
|
||||
/// A `package:` style import path to the file where the class is defined.
|
||||
final String import;
|
||||
/// The class that this class extends or implements. This is the only optional
|
||||
/// field.
|
||||
final String superClass;
|
||||
|
||||
AssetId get assetId => new AssetId(package, packagePath);
|
||||
String get package => path.split(import.replaceFirst('package:', '')).first;
|
||||
String get packagePath => path.joinAll(['lib']
|
||||
..addAll(path.split(import.replaceFirst('package:', ''))..removeAt(0)));
|
||||
|
||||
const AnnotationDescriptor(this.name, this.import, this.superClass);
|
||||
_implementsWithWarning(firstMatch(annotation.name, assetId), VIEWS);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
library angular2.transform.common.class_matcher_base;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:barback/barback.dart' show AssetId;
|
||||
import 'package:code_transformers/assets.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'logging.dart' show logger;
|
||||
|
||||
/// Checks if a given [Identifier] matches any of the given [ClassDescriptor]s.
|
||||
abstract class ClassMatcherBase {
|
||||
/// Always start out with the default angular [ClassDescriptor]s.
|
||||
final List<ClassDescriptor> _classDescriptors;
|
||||
|
||||
ClassMatcherBase(this._classDescriptors);
|
||||
|
||||
/// Adds a new [ClassDescriptor].
|
||||
void add(ClassDescriptor classDescriptor) =>
|
||||
_classDescriptors.add(classDescriptor);
|
||||
|
||||
/// Adds a number of [ClassDescriptor]s.
|
||||
void addAll(Iterable<ClassDescriptor> classDescriptors) =>
|
||||
_classDescriptors.addAll(classDescriptors);
|
||||
|
||||
/// Returns the first [ClassDescriptor] that matches the given
|
||||
/// [Identifier] node which appears in `assetId`.
|
||||
ClassDescriptor firstMatch(Identifier className, AssetId assetId) =>
|
||||
_classDescriptors.firstWhere((a) => isMatch(className, a, assetId),
|
||||
orElse: () => null);
|
||||
|
||||
/// Checks whether an [Identifier] matches any [ClassDescriptor].
|
||||
bool hasMatch(Identifier className, AssetId assetId) =>
|
||||
_classDescriptors.any((a) => isMatch(className, a, assetId));
|
||||
|
||||
/// Checks whether an [Identifier] matches any [ClassDescriptor].
|
||||
ImportDirective getMatchingImport(Identifier className, AssetId assetId) {
|
||||
for (var d in _classDescriptors) {
|
||||
var matchingImport = _getMatchingImport(className, d, assetId);
|
||||
if (matchingImport != null) {
|
||||
return matchingImport;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Checks if `descriptor` extends or is any of the supplied `interfaces`.
|
||||
bool implements(ClassDescriptor descriptor, List<ClassDescriptor> interfaces,
|
||||
{String missingSuperClassWarning}) {
|
||||
if (descriptor == null) return false;
|
||||
if (interfaces.contains(descriptor)) return true;
|
||||
if (descriptor.superClass == null) return false;
|
||||
var superClass = _classDescriptors.firstWhere(
|
||||
(a) => a.name == descriptor.superClass, orElse: () => null);
|
||||
if (superClass == null) {
|
||||
if (missingSuperClassWarning != null &&
|
||||
missingSuperClassWarning.isNotEmpty) {
|
||||
logger.warning(missingSuperClassWarning);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return implements(superClass, interfaces);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an [ImportDirective] matching `descriptor` for `className` which appears in `assetId`, or `null` if none exists.
|
||||
ImportDirective _getMatchingImport(
|
||||
Identifier className, ClassDescriptor descriptor, AssetId assetId) {
|
||||
if (className == null) return null;
|
||||
String name;
|
||||
Identifier prefix;
|
||||
if (className is PrefixedIdentifier) {
|
||||
name = className.identifier.name;
|
||||
prefix = className.prefix;
|
||||
} else {
|
||||
name = className.name;
|
||||
}
|
||||
if (name != descriptor.name) return null;
|
||||
return (className.root as CompilationUnit).directives
|
||||
.where((d) => d is ImportDirective)
|
||||
.firstWhere((ImportDirective i) {
|
||||
var importMatch = false;
|
||||
var uriString = i.uri.stringValue;
|
||||
if (uriString == descriptor.import) {
|
||||
importMatch = true;
|
||||
} else if (uriString.startsWith('package:') ||
|
||||
uriString.startsWith('dart:')) {
|
||||
return false;
|
||||
} else {
|
||||
importMatch =
|
||||
descriptor.assetId == uriToAssetId(assetId, uriString, logger, null);
|
||||
}
|
||||
|
||||
if (!importMatch) return false;
|
||||
if (prefix == null) return i.prefix == null;
|
||||
if (i.prefix == null) return false;
|
||||
return prefix.name == i.prefix.name;
|
||||
}, orElse: () => null);
|
||||
}
|
||||
|
||||
// Checks if `className` which appears in `assetId` matches a [ClassDescriptor].
|
||||
bool isMatch(
|
||||
Identifier className, ClassDescriptor descriptor, AssetId assetId) {
|
||||
return _getMatchingImport(className, descriptor, assetId) != null;
|
||||
}
|
||||
|
||||
/// String based description of a class and its location.
|
||||
class ClassDescriptor {
|
||||
/// The name of the class.
|
||||
final String name;
|
||||
/// A `package:` style import path to the file where the class is defined.
|
||||
final String import;
|
||||
/// The class that this class extends or implements. This is the only optional
|
||||
/// field.
|
||||
final String superClass;
|
||||
|
||||
AssetId get assetId => new AssetId(package, packagePath);
|
||||
String get package => path.split(import.replaceFirst('package:', '')).first;
|
||||
String get packagePath => path.joinAll(['lib']
|
||||
..addAll(path.split(import.replaceFirst('package:', ''))..removeAt(0)));
|
||||
|
||||
const ClassDescriptor(this.name, this.import, {this.superClass});
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
library angular2.transform.common.annotati_ON_matcher;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:barback/barback.dart' show AssetId;
|
||||
import 'class_matcher_base.dart';
|
||||
|
||||
export 'class_matcher_base.dart' show ClassDescriptor;
|
||||
|
||||
/// [ClassDescriptor]s for the default angular interfaces that may be
|
||||
/// implemented by a class. These classes are re-exported in many places so this
|
||||
/// covers all libraries which provide them.
|
||||
const _ON_CHANGE_INTERFACES = const [
|
||||
const ClassDescriptor('OnChange', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor('OnChange', 'package:angular2/annotations.dart'),
|
||||
const ClassDescriptor(
|
||||
'OnChange', 'package:angular2/src/core/compiler/interfaces.dart'),
|
||||
];
|
||||
const _ON_DESTROY_INTERFACES = const [
|
||||
const ClassDescriptor('OnDestroy', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor('OnDestroy', 'package:angular2/annotations.dart'),
|
||||
const ClassDescriptor(
|
||||
'OnDestroy', 'package:angular2/src/core/compiler/interfaces.dart'),
|
||||
];
|
||||
const _ON_CHECK_INTERFACES = const [
|
||||
const ClassDescriptor('OnCheck', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor('OnCheck', 'package:angular2/annotations.dart'),
|
||||
const ClassDescriptor(
|
||||
'OnCheck', 'package:angular2/src/core/compiler/interfaces.dart'),
|
||||
];
|
||||
const _ON_INIT_INTERFACES = const [
|
||||
const ClassDescriptor('OnInit', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor('OnInit', 'package:angular2/annotations.dart'),
|
||||
const ClassDescriptor(
|
||||
'OnInit', 'package:angular2/src/core/compiler/interfaces.dart'),
|
||||
];
|
||||
const _ON_ALL_CHANGES_DONE_INTERFACES = const [
|
||||
const ClassDescriptor('OnAllChangesDone', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor(
|
||||
'OnAllChangesDone', 'package:angular2/annotations.dart'),
|
||||
const ClassDescriptor(
|
||||
'OnAllChangesDone', 'package:angular2/src/core/compiler/interfaces.dart')
|
||||
];
|
||||
|
||||
/// Checks if a given [Annotation] matches any of the given
|
||||
/// [ClassDescriptors].
|
||||
class InterfaceMatcher extends ClassMatcherBase {
|
||||
InterfaceMatcher._(classDescriptors) : super(classDescriptors);
|
||||
|
||||
factory InterfaceMatcher() {
|
||||
return new InterfaceMatcher._([]
|
||||
..addAll(_ON_CHANGE_INTERFACES)
|
||||
..addAll(_ON_DESTROY_INTERFACES)
|
||||
..addAll(_ON_CHECK_INTERFACES)
|
||||
..addAll(_ON_INIT_INTERFACES)
|
||||
..addAll(_ON_ALL_CHANGES_DONE_INTERFACES));
|
||||
}
|
||||
|
||||
/// Checks if an [Identifier] implements [OnChange].
|
||||
bool isOnChange(Identifier typeName, AssetId assetId) =>
|
||||
implements(firstMatch(typeName, assetId), _ON_CHANGE_INTERFACES);
|
||||
|
||||
/// Checks if an [Identifier] implements [OnDestroy].
|
||||
bool isOnDestroy(Identifier typeName, AssetId assetId) =>
|
||||
implements(firstMatch(typeName, assetId), _ON_DESTROY_INTERFACES);
|
||||
|
||||
/// Checks if an [Identifier] implements [OnCheck].
|
||||
bool isOnCheck(Identifier typeName, AssetId assetId) =>
|
||||
implements(firstMatch(typeName, assetId), _ON_CHECK_INTERFACES);
|
||||
|
||||
/// Checks if an [Identifier] implements [OnInit].
|
||||
bool isOnInit(Identifier typeName, AssetId assetId) =>
|
||||
implements(firstMatch(typeName, assetId), _ON_INIT_INTERFACES);
|
||||
|
||||
/// Checks if an [Identifier] implements [OnAllChangesDone].
|
||||
bool isOnAllChangesDone(Identifier typeName, AssetId assetId) => implements(
|
||||
firstMatch(typeName, assetId), _ON_ALL_CHANGES_DONE_INTERFACES);
|
||||
}
|
|
@ -59,7 +59,7 @@ class TransformerOptions {
|
|||
factory TransformerOptions(List<String> entryPoints,
|
||||
{List<String> reflectionEntryPoints, String modeName: 'release',
|
||||
MirrorMode mirrorMode: MirrorMode.none, bool initReflector: true,
|
||||
List<AnnotationDescriptor> customAnnotationDescriptors: const [],
|
||||
List<ClassDescriptor> customAnnotationDescriptors: const [],
|
||||
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES,
|
||||
bool inlineViews: true, bool generateChangeDetectors: true}) {
|
||||
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
|
||||
|
|
|
@ -83,8 +83,8 @@ int _readInt(Map config, String paramName, {int defaultValue: null}) {
|
|||
}
|
||||
|
||||
/// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into
|
||||
/// [AnnotationDescriptor]s.
|
||||
List<AnnotationDescriptor> _readCustomAnnotations(Map config) {
|
||||
/// [ClassDescriptor]s.
|
||||
List<ClassDescriptor> _readCustomAnnotations(Map config) {
|
||||
var descriptors = [];
|
||||
var customAnnotations = config[CUSTOM_ANNOTATIONS_PARAM];
|
||||
if (customAnnotations == null) return descriptors;
|
||||
|
@ -104,7 +104,8 @@ List<AnnotationDescriptor> _readCustomAnnotations(Map config) {
|
|||
error = true;
|
||||
continue;
|
||||
}
|
||||
descriptors.add(new AnnotationDescriptor(name, import, superClass));
|
||||
descriptors
|
||||
.add(new ClassDescriptor(name, import, superClass: superClass));
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:angular2/src/render/xhr.dart' show XHR;
|
|||
import 'package:angular2/src/transform/common/annotation_matcher.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/async_string_writer.dart';
|
||||
import 'package:angular2/src/transform/common/interface_matcher.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/xhr_impl.dart';
|
||||
|
@ -22,13 +23,15 @@ import 'visitors.dart';
|
|||
/// If no Angular 2 `Directive`s are found in `code`, returns the empty
|
||||
/// string unless `forceGenerate` is true, in which case an empty ngDeps
|
||||
/// file is created.
|
||||
Future<String> createNgDeps(AssetReader reader, AssetId assetId,
|
||||
AnnotationMatcher annotationMatcher, bool inlineViews) async {
|
||||
Future<String> createNgDeps(
|
||||
AssetReader reader, AssetId assetId, AnnotationMatcher annotationMatcher,
|
||||
{bool inlineViews}) async {
|
||||
// TODO(kegluneq): Shortcut if we can determine that there are no
|
||||
// [Directive]s present, taking into account `export`s.
|
||||
var writer = new AsyncStringWriter();
|
||||
var visitor = new CreateNgDepsVisitor(writer, assetId,
|
||||
new XhrImpl(reader, assetId), annotationMatcher, inlineViews);
|
||||
new XhrImpl(reader, assetId), annotationMatcher, _interfaceMatcher,
|
||||
inlineViews: inlineViews);
|
||||
var code = await reader.readAsString(assetId);
|
||||
parseCompilationUnit(code, name: assetId.path).accept(visitor);
|
||||
|
||||
|
@ -40,6 +43,8 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
|
|||
return await writer.asyncToString();
|
||||
}
|
||||
|
||||
InterfaceMatcher _interfaceMatcher = new InterfaceMatcher();
|
||||
|
||||
/// Visitor responsible for processing [CompilationUnit] and creating an
|
||||
/// associated .ng_deps.dart file.
|
||||
class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
|
||||
|
@ -56,18 +61,24 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
|
|||
final ParameterTransformVisitor _paramsVisitor;
|
||||
final AnnotationsTransformVisitor _metaVisitor;
|
||||
final AnnotationMatcher _annotationMatcher;
|
||||
final InterfaceMatcher _interfaceMatcher;
|
||||
|
||||
/// The assetId for the file which we are parsing.
|
||||
final AssetId assetId;
|
||||
|
||||
CreateNgDepsVisitor(AsyncStringWriter writer, this.assetId, XHR xhr,
|
||||
this._annotationMatcher, inlineViews)
|
||||
CreateNgDepsVisitor(AsyncStringWriter writer, AssetId assetId, XHR xhr,
|
||||
AnnotationMatcher annotationMatcher, InterfaceMatcher interfaceMatcher,
|
||||
{bool inlineViews})
|
||||
: writer = writer,
|
||||
_copyVisitor = new ToSourceVisitor(writer),
|
||||
_factoryVisitor = new FactoryTransformVisitor(writer),
|
||||
_paramsVisitor = new ParameterTransformVisitor(writer),
|
||||
_metaVisitor = new AnnotationsTransformVisitor(
|
||||
writer, xhr, inlineViews);
|
||||
writer, xhr, annotationMatcher, interfaceMatcher, assetId,
|
||||
inlineViews: inlineViews),
|
||||
_annotationMatcher = annotationMatcher,
|
||||
_interfaceMatcher = interfaceMatcher,
|
||||
this.assetId = assetId;
|
||||
|
||||
void _visitNodeListWithSeparator(NodeList<AstNode> list, String separator) {
|
||||
if (list == null) return;
|
||||
|
@ -174,7 +185,8 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
|
|||
|
||||
@override
|
||||
Object visitClassDeclaration(ClassDeclaration node) {
|
||||
if (!node.metadata.any((a) => _annotationMatcher.hasMatch(a, assetId))) {
|
||||
if (!node.metadata
|
||||
.any((a) => _annotationMatcher.hasMatch(a.name, assetId))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -200,11 +212,12 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
|
|||
if (node.implementsClause != null &&
|
||||
node.implementsClause.interfaces != null &&
|
||||
node.implementsClause.interfaces.isNotEmpty) {
|
||||
writer.print(''', 'interfaces': const [''');
|
||||
writer.print(node.implementsClause.interfaces
|
||||
.map((interface) => interface.name)
|
||||
.join(', '));
|
||||
writer.print(']');
|
||||
writer
|
||||
..print(''', 'interfaces': const [''')
|
||||
..print(node.implementsClause.interfaces
|
||||
.map((interface) => interface.name)
|
||||
.join(', '))
|
||||
..print(']');
|
||||
}
|
||||
writer.print('})');
|
||||
return null;
|
||||
|
@ -243,7 +256,8 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
|
|||
|
||||
@override
|
||||
bool visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if (!node.metadata.any((a) => _annotationMatcher.hasMatch(a, assetId))) {
|
||||
if (!node.metadata
|
||||
.any((a) => _annotationMatcher.hasMatch(a.name, assetId))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,8 @@ class DirectiveProcessor extends Transformer {
|
|||
var asset = transform.primaryInput;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var ngDepsSrc = await createNgDeps(
|
||||
reader, asset.id, options.annotationMatcher, options.inlineViews);
|
||||
reader, asset.id, options.annotationMatcher,
|
||||
inlineViews: options.inlineViews);
|
||||
if (ngDepsSrc != null && ngDepsSrc.isNotEmpty) {
|
||||
var ngDepsAssetId =
|
||||
transform.primaryInput.id.changeExtension(DEPS_EXTENSION);
|
||||
|
|
|
@ -4,8 +4,11 @@ import 'dart:async';
|
|||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:analyzer/src/generated/java_core.dart';
|
||||
import 'package:angular2/src/render/xhr.dart' show XHR;
|
||||
import 'package:angular2/src/transform/common/annotation_matcher.dart';
|
||||
import 'package:angular2/src/transform/common/async_string_writer.dart';
|
||||
import 'package:angular2/src/transform/common/interface_matcher.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
/// `ToSourceVisitor` designed to accept {@link ConstructorDeclaration} nodes.
|
||||
class _CtorTransformVisitor extends ToSourceVisitor {
|
||||
|
@ -203,25 +206,84 @@ class FactoryTransformVisitor extends _CtorTransformVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(kegluenq): Use pull #1772 to detect when available.
|
||||
bool _isViewAnnotation(Annotation node) => '${node.name}' == 'View';
|
||||
|
||||
/// ToSourceVisitor designed to print a `ClassDeclaration` node as a
|
||||
/// 'annotations' value for Angular2's `registerType` calls.
|
||||
class AnnotationsTransformVisitor extends ToSourceVisitor {
|
||||
final AsyncStringWriter writer;
|
||||
final XHR _xhr;
|
||||
final AnnotationMatcher _annotationMatcher;
|
||||
final InterfaceMatcher _interfaceMatcher;
|
||||
final AssetId _assetId;
|
||||
final bool _inlineViews;
|
||||
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||
bool _processingView = false;
|
||||
bool _isProcessingView = false;
|
||||
bool _isProcessingDirective = false;
|
||||
String _lifecycleValue = null;
|
||||
|
||||
AnnotationsTransformVisitor(
|
||||
AsyncStringWriter writer, this._xhr, this._inlineViews)
|
||||
AnnotationsTransformVisitor(AsyncStringWriter writer, this._xhr,
|
||||
this._annotationMatcher, this._interfaceMatcher, this._assetId,
|
||||
{bool inlineViews})
|
||||
: this.writer = writer,
|
||||
_inlineViews = inlineViews,
|
||||
super(writer);
|
||||
|
||||
/// Determines if the `node` has interface-based lifecycle methods and
|
||||
/// populates `_lifecycleValue` with the appropriate values if so. If none are
|
||||
/// present, `_lifecycleValue` is not modified.
|
||||
void _populateLifecycleValue(ClassDeclaration node) {
|
||||
var lifecycleEntries = [];
|
||||
var prefix = '';
|
||||
var populateImport = (Identifier name) {
|
||||
if (prefix.isNotEmpty) return;
|
||||
var import = _interfaceMatcher.getMatchingImport(name, _assetId);
|
||||
prefix =
|
||||
import != null && import.prefix != null ? '${import.prefix}.' : '';
|
||||
};
|
||||
|
||||
var namesToTest = [];
|
||||
|
||||
if (node.implementsClause != null &&
|
||||
node.implementsClause.interfaces != null &&
|
||||
node.implementsClause.interfaces.isNotEmpty) {
|
||||
namesToTest.addAll(node.implementsClause.interfaces.map((i) => i.name));
|
||||
}
|
||||
|
||||
if (node.extendsClause != null) {
|
||||
namesToTest.add(node.extendsClause.superclass.name);
|
||||
}
|
||||
|
||||
namesToTest.forEach((name) {
|
||||
if (_interfaceMatcher.isOnChange(name, _assetId)) {
|
||||
lifecycleEntries.add('onChange');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnDestroy(name, _assetId)) {
|
||||
lifecycleEntries.add('onDestroy');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnCheck(name, _assetId)) {
|
||||
lifecycleEntries.add('onCheck');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnInit(name, _assetId)) {
|
||||
lifecycleEntries.add('onInit');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnAllChangesDone(name, _assetId)) {
|
||||
lifecycleEntries.add('onAllChangesDone');
|
||||
populateImport(name);
|
||||
}
|
||||
});
|
||||
if (lifecycleEntries.isNotEmpty) {
|
||||
_lifecycleValue = 'const [${prefix}LifecycleEvent.'
|
||||
'${lifecycleEntries.join(", ${prefix}LifecycleEvent.")}]';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitClassDeclaration(ClassDeclaration node) {
|
||||
_populateLifecycleValue(node);
|
||||
|
||||
writer.print('const [');
|
||||
var size = node.metadata.length;
|
||||
for (var i = 0; i < size; ++i) {
|
||||
|
@ -231,6 +293,8 @@ class AnnotationsTransformVisitor extends ToSourceVisitor {
|
|||
node.metadata[i].accept(this);
|
||||
}
|
||||
writer.print(']');
|
||||
|
||||
_lifecycleValue = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -238,15 +302,30 @@ class AnnotationsTransformVisitor extends ToSourceVisitor {
|
|||
Object visitAnnotation(Annotation node) {
|
||||
writer.print('const ');
|
||||
if (node.name != null) {
|
||||
_processingView = _isViewAnnotation(node);
|
||||
_isProcessingDirective = _annotationMatcher.isDirective(node, _assetId);
|
||||
_isProcessingView = _annotationMatcher.isView(node, _assetId);
|
||||
node.name.accept(this);
|
||||
} else {
|
||||
_isProcessingDirective = false;
|
||||
_isProcessingView = false;
|
||||
}
|
||||
if (node.constructorName != null) {
|
||||
writer.print('.');
|
||||
node.constructorName.accept(this);
|
||||
}
|
||||
if (node.arguments != null) {
|
||||
node.arguments.accept(this);
|
||||
if (node.arguments != null && node.arguments.arguments != null) {
|
||||
var args = node.arguments.arguments;
|
||||
writer.print('(');
|
||||
for (var i = 0, iLen = args.length; i < iLen; ++i) {
|
||||
if (i != 0) {
|
||||
writer.print(', ');
|
||||
}
|
||||
args[i].accept(this);
|
||||
}
|
||||
if (_lifecycleValue != null && _isProcessingDirective) {
|
||||
writer.print(''', lifecycle: $_lifecycleValue ''');
|
||||
}
|
||||
writer.print(')');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -255,7 +334,7 @@ class AnnotationsTransformVisitor extends ToSourceVisitor {
|
|||
@override
|
||||
Object visitNamedExpression(NamedExpression node) {
|
||||
// TODO(kegluneq): Remove this limitation.
|
||||
if (!_processingView ||
|
||||
if (!_isProcessingView ||
|
||||
node.name is! Label ||
|
||||
node.name.label is! SimpleIdentifier) {
|
||||
return super.visitNamedExpression(node);
|
||||
|
|
|
@ -24,20 +24,22 @@ void allTests() {
|
|||
_testNgDeps('should recognize custom annotations with package: imports',
|
||||
'custom_metadata/package_soup.dart',
|
||||
customDescriptors: [
|
||||
const AnnotationDescriptor('Soup', 'package:soup/soup.dart', 'Component'),
|
||||
const ClassDescriptor('Soup', 'package:soup/soup.dart',
|
||||
superClass: 'Component'),
|
||||
]);
|
||||
|
||||
_testNgDeps('should recognize custom annotations with relative imports',
|
||||
'custom_metadata/relative_soup.dart',
|
||||
assetId: new AssetId('soup', 'lib/relative_soup.dart'),
|
||||
customDescriptors: [
|
||||
const AnnotationDescriptor(
|
||||
'Soup', 'package:soup/annotations/soup.dart', 'Component'),
|
||||
const ClassDescriptor('Soup', 'package:soup/annotations/soup.dart',
|
||||
superClass: 'Component'),
|
||||
]);
|
||||
|
||||
_testNgDeps('Requires the specified import.', 'custom_metadata/bad_soup.dart',
|
||||
customDescriptors: [
|
||||
const AnnotationDescriptor('Soup', 'package:soup/soup.dart', 'Component'),
|
||||
const ClassDescriptor('Soup', 'package:soup/soup.dart',
|
||||
superClass: 'Component'),
|
||||
]);
|
||||
|
||||
_testNgDeps(
|
||||
|
@ -81,6 +83,20 @@ void allTests() {
|
|||
_testNgDeps('should not include superclasses in `interfaces`.',
|
||||
'superclass_files/soup.dart');
|
||||
|
||||
_testNgDeps(
|
||||
'should populate `lifecycle` when lifecycle interfaces are present.',
|
||||
'interface_lifecycle_files/soup.dart');
|
||||
|
||||
_testNgDeps('should populate multiple `lifecycle` values when necessary.',
|
||||
'multiple_interface_lifecycle_files/soup.dart');
|
||||
|
||||
_testNgDeps(
|
||||
'should populate `lifecycle` when lifecycle superclass is present.',
|
||||
'superclass_lifecycle_files/soup.dart');
|
||||
|
||||
_testNgDeps('should populate `lifecycle` with prefix when necessary.',
|
||||
'prefixed_interface_lifecycle_files/soup.dart');
|
||||
|
||||
_testNgDeps(
|
||||
'should not throw/hang on invalid urls', 'invalid_url_files/hello.dart',
|
||||
expectedLogs: [
|
||||
|
@ -119,8 +135,8 @@ void _testNgDeps(String name, String inputPath,
|
|||
var expectedId = _assetIdForPath(expectedPath);
|
||||
|
||||
var annotationMatcher = new AnnotationMatcher()..addAll(customDescriptors);
|
||||
var output =
|
||||
await createNgDeps(reader, inputId, annotationMatcher, inlineViews);
|
||||
var output = await createNgDeps(reader, inputId, annotationMatcher,
|
||||
inlineViews: inlineViews);
|
||||
if (output == null) {
|
||||
expect(await reader.hasInput(expectedId)).toBeFalse();
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
library dinner.soup.ng_deps.dart;
|
||||
|
||||
import 'soup.dart';
|
||||
export 'soup.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart' as _ngRef;
|
||||
import 'package:angular2/annotations.dart';
|
||||
|
||||
var _visited = false;
|
||||
void initReflector() {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
_ngRef.reflector
|
||||
..registerType(OnChangeSoupComponent, {
|
||||
'factory': () => new OnChangeSoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const Component(
|
||||
selector: '[soup]', lifecycle: const [LifecycleEvent.onChange])
|
||||
],
|
||||
'interfaces': const [OnChange]
|
||||
})
|
||||
..registerType(OnDestroySoupComponent, {
|
||||
'factory': () => new OnDestroySoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const Component(
|
||||
selector: '[soup]', lifecycle: const [LifecycleEvent.onDestroy])
|
||||
],
|
||||
'interfaces': const [OnDestroy]
|
||||
})
|
||||
..registerType(OnCheckSoupComponent, {
|
||||
'factory': () => new OnCheckSoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const Component(
|
||||
selector: '[soup]', lifecycle: const [LifecycleEvent.onCheck])
|
||||
],
|
||||
'interfaces': const [OnCheck]
|
||||
})
|
||||
..registerType(OnInitSoupComponent, {
|
||||
'factory': () => new OnInitSoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const Component(
|
||||
selector: '[soup]', lifecycle: const [LifecycleEvent.onInit])
|
||||
],
|
||||
'interfaces': const [OnInit]
|
||||
})
|
||||
..registerType(OnAllChangesDoneSoupComponent, {
|
||||
'factory': () => new OnAllChangesDoneSoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const Component(
|
||||
selector: '[soup]',
|
||||
lifecycle: const [LifecycleEvent.onAllChangesDone])
|
||||
],
|
||||
'interfaces': const [OnAllChangesDone]
|
||||
});
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
library dinner.soup;
|
||||
|
||||
import 'package:angular2/annotations.dart';
|
||||
|
||||
@Component(selector: '[soup]')
|
||||
class OnChangeSoupComponent implements OnChange {}
|
||||
|
||||
@Component(selector: '[soup]')
|
||||
class OnDestroySoupComponent implements OnDestroy {}
|
||||
|
||||
@Component(selector: '[soup]')
|
||||
class OnCheckSoupComponent implements OnCheck {}
|
||||
|
||||
@Component(selector: '[soup]')
|
||||
class OnInitSoupComponent implements OnInit {}
|
||||
|
||||
@Component(selector: '[soup]')
|
||||
class OnAllChangesDoneSoupComponent implements OnAllChangesDone {}
|
|
@ -0,0 +1,27 @@
|
|||
library dinner.soup.ng_deps.dart;
|
||||
|
||||
import 'soup.dart';
|
||||
export 'soup.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart' as _ngRef;
|
||||
import 'package:angular2/annotations.dart';
|
||||
|
||||
var _visited = false;
|
||||
void initReflector() {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
_ngRef.reflector
|
||||
..registerType(MultiSoupComponent, {
|
||||
'factory': () => new MultiSoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const Component(
|
||||
selector: '[soup]',
|
||||
lifecycle: const [
|
||||
LifecycleEvent.onChange,
|
||||
LifecycleEvent.onDestroy,
|
||||
LifecycleEvent.onInit
|
||||
])
|
||||
],
|
||||
'interfaces': const [OnChange, OnDestroy, OnInit]
|
||||
});
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
library dinner.soup;
|
||||
|
||||
import 'package:angular2/annotations.dart';
|
||||
|
||||
@Component(selector: '[soup]')
|
||||
class MultiSoupComponent implements OnChange, OnDestroy, OnInit {}
|
|
@ -0,0 +1,23 @@
|
|||
library dinner.soup.ng_deps.dart;
|
||||
|
||||
import 'soup.dart';
|
||||
export 'soup.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart' as _ngRef;
|
||||
import 'package:angular2/annotations.dart' as prefix;
|
||||
|
||||
var _visited = false;
|
||||
void initReflector() {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
_ngRef.reflector
|
||||
..registerType(OnChangeSoupComponent, {
|
||||
'factory': () => new OnChangeSoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const prefix.Component(
|
||||
selector: '[soup]',
|
||||
lifecycle: const [prefix.LifecycleEvent.onChange])
|
||||
],
|
||||
'interfaces': const [prefix.OnChange]
|
||||
});
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
library dinner.soup;
|
||||
|
||||
import 'package:angular2/annotations.dart' as prefix;
|
||||
|
||||
@prefix.Component(selector: '[soup]')
|
||||
class OnChangeSoupComponent implements prefix.OnChange {}
|
|
@ -0,0 +1,21 @@
|
|||
library dinner.soup.ng_deps.dart;
|
||||
|
||||
import 'soup.dart';
|
||||
export 'soup.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart' as _ngRef;
|
||||
import 'package:angular2/annotations.dart';
|
||||
|
||||
var _visited = false;
|
||||
void initReflector() {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
_ngRef.reflector
|
||||
..registerType(OnChangeSoupComponent, {
|
||||
'factory': () => new OnChangeSoupComponent(),
|
||||
'parameters': const [],
|
||||
'annotations': const [
|
||||
const Component(
|
||||
selector: '[soup]', lifecycle: const [LifecycleEvent.onChange])
|
||||
]
|
||||
});
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
library dinner.soup;
|
||||
|
||||
import 'package:angular2/annotations.dart';
|
||||
|
||||
@Component(selector: '[soup]')
|
||||
class OnChangeSoupComponent extends OnChange {}
|
Loading…
Reference in New Issue