chore(transform): move transform module to modules_dart
The build/pure-packages.dart gulp task has also been updated to move the files into the angular2 tree. Closes #3729
This commit is contained in:
@ -0,0 +1,114 @@
|
||||
library angular2.transform.bind_generator.generator;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/ng_deps.dart';
|
||||
import 'package:angular2/src/transform/common/property_utils.dart' as prop;
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'visitor.dart';
|
||||
|
||||
Future<String> createNgSettersAndGetters(
|
||||
AssetReader reader, AssetId entryPoint) async {
|
||||
NgDeps ngDeps = await NgDeps.parse(reader, entryPoint);
|
||||
|
||||
String code = ngDeps.code;
|
||||
var setters = _generateSetters(_createPropertiesMap(ngDeps));
|
||||
var getters = _generateGetters(_createEventPropertiesList(ngDeps));
|
||||
|
||||
if (setters.isEmpty && getters.isEmpty) return code;
|
||||
var out = new StringBuffer();
|
||||
var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end;
|
||||
out.write(code.substring(0, codeInjectIdx));
|
||||
if (setters.isNotEmpty) {
|
||||
out.write('..registerSetters({${setters.join(', ')}})');
|
||||
}
|
||||
if (getters.isNotEmpty) {
|
||||
out.write('..registerGetters({${getters.join(', ')}})');
|
||||
}
|
||||
out.write(code.substring(codeInjectIdx));
|
||||
return '$out';
|
||||
}
|
||||
|
||||
// TODO(kegluneq): De-dupe from template_compiler/generator.dart, #3589.
|
||||
|
||||
/// Consumes the map generated by {@link _createPropertiesMap} to codegen
|
||||
/// setters.
|
||||
List<String> _generateSetters(Map<String, String> bindMap) {
|
||||
var setters = [];
|
||||
// TODO(kegluneq): Include types for receivers. See #886.
|
||||
bindMap.forEach((setterName, type) {
|
||||
if (!prop.isValid(setterName)) {
|
||||
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
|
||||
setters.add(prop.lazyInvalidSetter(setterName));
|
||||
} else {
|
||||
setters.add(''' '${prop.sanitize(setterName)}': '''
|
||||
''' (o, v) => o.$setterName = v ''');
|
||||
}
|
||||
});
|
||||
return setters;
|
||||
}
|
||||
|
||||
/// Collapses all `properties` in {@link ngDeps} into a map where the keys are
|
||||
/// the bind properties and the values are either the one and only type
|
||||
/// binding to that property or the empty string.
|
||||
Map<String, String> _createPropertiesMap(NgDeps ngDeps) {
|
||||
var visitor = new ExtractNamedExpressionVisitor('properties');
|
||||
var bindMap = {};
|
||||
ngDeps.registeredTypes.forEach((RegisteredType t) {
|
||||
visitor.bindConfig.clear();
|
||||
t.annotations.accept(visitor);
|
||||
visitor.bindConfig.forEach((String config) {
|
||||
// See comments for `Directive` in annotations_impl/annotations.ts for
|
||||
// details on how `properties` is specified.
|
||||
var prop;
|
||||
var idx = config.indexOf(':');
|
||||
if (idx > 0) {
|
||||
prop = config.substring(0, idx).trim();
|
||||
} else {
|
||||
prop = config;
|
||||
}
|
||||
if (bindMap.containsKey(prop)) {
|
||||
bindMap[prop] = '';
|
||||
} else {
|
||||
bindMap[prop] = '${t.typeName}';
|
||||
}
|
||||
});
|
||||
});
|
||||
return bindMap;
|
||||
}
|
||||
|
||||
/// Consumes the list generated by {@link _createEventPropertiesList} to codegen
|
||||
/// getters.
|
||||
List<String> _generateGetters(List<String> eventProperties) {
|
||||
var getters = [];
|
||||
// TODO(kegluneq): Include types for receivers. See #886.
|
||||
for (var property in eventProperties) {
|
||||
if (!prop.isValid(property)) {
|
||||
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
|
||||
getters.add(prop.lazyInvalidGetter(property));
|
||||
} else {
|
||||
getters.add(''' '${prop.sanitize(property)}': (o) => o.$property''');
|
||||
}
|
||||
}
|
||||
return getters;
|
||||
}
|
||||
|
||||
/// Collapses all `events` in {@link ngDeps} into a list of corresponding
|
||||
/// property names.
|
||||
List<String> _createEventPropertiesList(NgDeps ngDeps) {
|
||||
var visitor = new ExtractNamedExpressionVisitor('events');
|
||||
var propertyNames = [];
|
||||
ngDeps.registeredTypes.forEach((RegisteredType t) {
|
||||
visitor.bindConfig.clear();
|
||||
t.annotations.accept(visitor);
|
||||
visitor.bindConfig.forEach((String config) {
|
||||
// See comments for `Directive` in annotations_impl/annotations.ts for
|
||||
// details on how `events` is specified. We are pulling out the property
|
||||
// name only (everything before the first `:`).
|
||||
propertyNames.add(config.split(':').first.trim());
|
||||
});
|
||||
});
|
||||
return propertyNames;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
library angular2.transform.bind_generator.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/options.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'generator.dart';
|
||||
|
||||
/// Transformer responsible for reading .ng_deps.dart files and generating
|
||||
/// setters from the "annotations" information in the generated
|
||||
/// `registerType` calls.
|
||||
///
|
||||
/// These setters are registered in the same `setupReflection` function with
|
||||
/// the `registerType` calls.
|
||||
class BindGenerator extends Transformer {
|
||||
final TransformerOptions options;
|
||||
|
||||
BindGenerator(this.options);
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
var id = transform.primaryInput.id;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var transformedCode = await createNgSettersAndGetters(reader, id);
|
||||
transform.addOutput(new Asset.fromString(
|
||||
id, formatter.format(transformedCode, uri: id.path)));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
library angular2.transform.bind_generator.visitor;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
|
||||
/// Visitor responsible for crawling the "annotations" value in a
|
||||
/// `registerType` call and pulling out the properties of any "bind"
|
||||
/// values found.
|
||||
class ExtractNamedExpressionVisitor extends Object
|
||||
with RecursiveAstVisitor<Object> {
|
||||
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||
final List<String> bindConfig = [];
|
||||
final String nameToExtract;
|
||||
|
||||
ExtractNamedExpressionVisitor(this.nameToExtract);
|
||||
|
||||
@override
|
||||
Object visitNamedExpression(NamedExpression node) {
|
||||
if ('${node.name.label}' == nameToExtract) {
|
||||
var evaluated = node.expression.accept(_evaluator);
|
||||
if (evaluated is List) {
|
||||
bindConfig.addAll(evaluated);
|
||||
} else {
|
||||
logger.error('`$nameToExtract` currently only supports List values');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return super.visitNamedExpression(node);
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
library angular2.transform.common.annotation_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 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 ClassDescriptor(
|
||||
'Injectable', 'package:angular2/src/di/decorators.dart'),
|
||||
const ClassDescriptor('Injectable', 'package:angular2/di.dart'),
|
||||
const ClassDescriptor('Injectable', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor('Injectable', 'package:angular2/bootstrap_static.dart'),
|
||||
];
|
||||
|
||||
const DIRECTIVES = const [
|
||||
const ClassDescriptor('Directive', 'package:angular2/src/core/metadatada/directive.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/src/core/metadata.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/metadata.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/angular2.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/core.dart',
|
||||
superClass: 'Injectable'),
|
||||
const ClassDescriptor('Directive', 'package:angular2/bootstrap_static.dart',
|
||||
superClass: 'Injectable'),
|
||||
];
|
||||
|
||||
const COMPONENTS = const [
|
||||
const ClassDescriptor('Component', 'package:angular2/src/core/metadata/directive.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/src/core/metadata.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/metadata.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/angular2.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/bootstrap_static.dart',
|
||||
superClass: 'Directive'),
|
||||
const ClassDescriptor('Component', 'package:angular2/core.dart',
|
||||
superClass: '`Directive'),
|
||||
];
|
||||
|
||||
const VIEWS = const [
|
||||
const ClassDescriptor('View', 'package:angular2/angular2.dart'),
|
||||
const ClassDescriptor('View', 'package:angular2/bootstrap_static.dart'),
|
||||
const ClassDescriptor('View', 'package:angular2/core.dart'),
|
||||
const ClassDescriptor('View', 'package:angular2/src/core/metadata/view.dart'),
|
||||
const ClassDescriptor('View', 'package:angular2/src/core/metadata.dart'),
|
||||
];
|
||||
|
||||
/// Checks if a given [Annotation] matches any of the given
|
||||
/// [ClassDescriptors].
|
||||
class AnnotationMatcher extends ClassMatcherBase {
|
||||
AnnotationMatcher._(classDescriptors) : super(classDescriptors);
|
||||
|
||||
factory AnnotationMatcher() {
|
||||
return new AnnotationMatcher._([]
|
||||
..addAll(COMPONENTS)
|
||||
..addAll(DIRECTIVES)
|
||||
..addAll(INJECTABLES)
|
||||
..addAll(VIEWS));
|
||||
}
|
||||
|
||||
bool _implementsWithWarning(Annotation annotation, AssetId assetId,
|
||||
List<ClassDescriptor> interfaces) {
|
||||
ClassDescriptor descriptor = firstMatch(annotation.name, assetId);
|
||||
if (descriptor == null) return false;
|
||||
return implements(descriptor, interfaces,
|
||||
missingSuperClassWarning:
|
||||
'Missing `custom_annotation` entry for `${descriptor.superClass}`.');
|
||||
}
|
||||
|
||||
/// Checks if an [Annotation] node implements [Injectable].
|
||||
bool isInjectable(Annotation annotation, AssetId assetId) =>
|
||||
_implementsWithWarning(annotation, assetId, INJECTABLES);
|
||||
|
||||
/// Checks if an [Annotation] node implements [Directive].
|
||||
bool isDirective(Annotation annotation, AssetId assetId) =>
|
||||
_implementsWithWarning(annotation, assetId, DIRECTIVES);
|
||||
|
||||
/// Checks if an [Annotation] node implements [Component].
|
||||
bool isComponent(Annotation annotation, AssetId assetId) =>
|
||||
_implementsWithWarning(annotation, assetId, COMPONENTS);
|
||||
|
||||
/// Checks if an [Annotation] node implements [View].
|
||||
bool isView(Annotation annotation, AssetId assetId) =>
|
||||
_implementsWithWarning(annotation, assetId, VIEWS);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
library angular2.transform.common.asset_reader;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
/// A class that allows fetching code using {@link AssetId}s without all the
|
||||
/// additional baggage of a {@link Transform}.
|
||||
abstract class AssetReader {
|
||||
Future<String> readAsString(AssetId id, {Encoding encoding});
|
||||
Future<bool> hasInput(AssetId id);
|
||||
|
||||
/// Creates an {@link AssetReader} using the `transform`, which should be a
|
||||
/// {@link Transform} or {@link AggregateTransform}.
|
||||
factory AssetReader.fromTransform(dynamic transform) =>
|
||||
new _TransformAssetReader(transform);
|
||||
}
|
||||
|
||||
class _TransformAssetReader implements AssetReader {
|
||||
final dynamic t;
|
||||
_TransformAssetReader(this.t);
|
||||
|
||||
Future<String> readAsString(AssetId id, {Encoding encoding}) =>
|
||||
t.readInputAsString(id, encoding: encoding);
|
||||
|
||||
Future<bool> hasInput(AssetId id) => t.hasInput(id);
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
library angular2.transform.common.async_string_writer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:analyzer/src/generated/java_core.dart';
|
||||
|
||||
/// [PrintWriter] implementation that allows asynchronous printing via
|
||||
/// [asyncPrint] and [asyncToString]. See those methods for details.
|
||||
class AsyncStringWriter extends PrintWriter {
|
||||
/// All [Future]s we are currently waiting on.
|
||||
final List<Future<String>> _toAwait = <Future<String>>[];
|
||||
final List<StringBuffer> _bufs;
|
||||
StringBuffer _curr;
|
||||
int _asyncCount = 0;
|
||||
|
||||
AsyncStringWriter._(StringBuffer curr)
|
||||
: _curr = curr,
|
||||
_bufs = <StringBuffer>[curr];
|
||||
|
||||
AsyncStringWriter([Object content = ""]) : this._(new StringBuffer(content));
|
||||
|
||||
void print(x) {
|
||||
_curr.write(x);
|
||||
}
|
||||
|
||||
/// Adds the result of `futureText` to the writer at the current position
|
||||
/// in the string being built. If using this method, you must use
|
||||
/// [asyncToString] instead of [toString] to get the value of the writer or
|
||||
/// your string may not appear as expected.
|
||||
Future<String> asyncPrint(Future<String> futureText) {
|
||||
_semaphoreIncrement();
|
||||
var myBuf = new StringBuffer();
|
||||
_bufs.add(myBuf);
|
||||
_curr = new StringBuffer();
|
||||
_bufs.add(_curr);
|
||||
|
||||
var toAwait = futureText.then((val) {
|
||||
myBuf.write(val);
|
||||
return val;
|
||||
});
|
||||
_toAwait.add(toAwait);
|
||||
return toAwait.whenComplete(() {
|
||||
_semaphoreDecrementAndCleanup();
|
||||
_toAwait.remove(toAwait);
|
||||
});
|
||||
}
|
||||
|
||||
/// Waits for any values added via [asyncPrint] and returns the fully
|
||||
/// built string.
|
||||
Future<String> asyncToString() {
|
||||
_semaphoreIncrement();
|
||||
var bufLen = _bufs.length;
|
||||
return Future.wait(_toAwait).then((_) {
|
||||
return _bufs.sublist(0, bufLen).join('');
|
||||
}).whenComplete(_semaphoreDecrementAndCleanup);
|
||||
}
|
||||
|
||||
String toString() => _bufs.map((buf) => '$buf').join('(async gap)');
|
||||
|
||||
void _semaphoreIncrement() {
|
||||
++_asyncCount;
|
||||
}
|
||||
|
||||
void _semaphoreDecrementAndCleanup() {
|
||||
assert(_asyncCount > 0);
|
||||
|
||||
--_asyncCount;
|
||||
if (_asyncCount == 0) {
|
||||
_curr = _bufs[0];
|
||||
for (var i = 1; i < _bufs.length; ++i) {
|
||||
_curr.write('${_bufs[i]}');
|
||||
}
|
||||
_bufs.removeRange(1, _bufs.length);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
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,288 @@
|
||||
library angular2.transform.common.directive_metadata_reader;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
|
||||
/// Reads [RenderDirectiveMetadata] from the `node`. `node` is expected to be an
|
||||
/// instance of [Annotation], [NodeList<Annotation>], ListLiteral, or
|
||||
/// [InstanceCreationExpression].
|
||||
RenderDirectiveMetadata readDirectiveMetadata(dynamic node) {
|
||||
assert(node is Annotation ||
|
||||
node is NodeList ||
|
||||
node is InstanceCreationExpression ||
|
||||
node is ListLiteral);
|
||||
var visitor = new _DirectiveMetadataVisitor();
|
||||
node.accept(visitor);
|
||||
return visitor.meta;
|
||||
}
|
||||
|
||||
num _getDirectiveType(String annotationName, Element element) {
|
||||
var byNameMatch = -1;
|
||||
// TODO(kegluneq): Detect subtypes & implementations of `Directive`s.
|
||||
switch (annotationName) {
|
||||
case 'Directive':
|
||||
byNameMatch = RenderDirectiveMetadata.DIRECTIVE_TYPE;
|
||||
break;
|
||||
case 'Component':
|
||||
byNameMatch = RenderDirectiveMetadata.COMPONENT_TYPE;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
if (element != null) {
|
||||
var byResolvedAst = -1;
|
||||
var libName = element.library.name;
|
||||
// If we have resolved, ensure the library is correct.
|
||||
if (libName == 'angular2.src.core.metadata.directives' ||
|
||||
libName == 'angular2.src.core.metadata') {
|
||||
byResolvedAst = byNameMatch;
|
||||
}
|
||||
// TODO(kegluneq): @keertip, can we expose this as a warning?
|
||||
assert(byNameMatch == byResolvedAst);
|
||||
}
|
||||
return byNameMatch;
|
||||
}
|
||||
|
||||
/// Visitor responsible for processing the `annotations` property of a
|
||||
/// [RegisterType] object and pulling out [RenderDirectiveMetadata].
|
||||
class _DirectiveMetadataVisitor extends Object
|
||||
with RecursiveAstVisitor<Object> {
|
||||
bool get _hasMeta => _type != null;
|
||||
|
||||
// Annotation fields
|
||||
num _type;
|
||||
String _selector;
|
||||
bool _compileChildren;
|
||||
List<String> _properties;
|
||||
Map<String, String> _host;
|
||||
List<String> _readAttributes;
|
||||
String _exportAs;
|
||||
bool _callOnDestroy;
|
||||
bool _callOnChange;
|
||||
bool _callOnCheck;
|
||||
bool _callOnInit;
|
||||
bool _callOnAllChangesDone;
|
||||
String _changeDetection;
|
||||
List<String> _events;
|
||||
|
||||
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||
|
||||
void _initializeMetadata(num directiveType) {
|
||||
assert(directiveType >= 0);
|
||||
|
||||
_type = directiveType;
|
||||
_selector = '';
|
||||
_compileChildren = true;
|
||||
_properties = [];
|
||||
_host = {};
|
||||
_readAttributes = [];
|
||||
_exportAs = null;
|
||||
_callOnDestroy = false;
|
||||
_callOnChange = false;
|
||||
_callOnCheck = false;
|
||||
_callOnInit = false;
|
||||
_callOnAllChangesDone = false;
|
||||
_changeDetection = null;
|
||||
_events = [];
|
||||
}
|
||||
|
||||
RenderDirectiveMetadata get meta => RenderDirectiveMetadata.create(
|
||||
type: _type,
|
||||
selector: _selector,
|
||||
compileChildren: _compileChildren,
|
||||
properties: _properties,
|
||||
host: _host,
|
||||
readAttributes: _readAttributes,
|
||||
exportAs: _exportAs,
|
||||
callOnDestroy: _callOnDestroy,
|
||||
callOnChange: _callOnChange,
|
||||
callOnCheck: _callOnCheck,
|
||||
callOnInit: _callOnInit,
|
||||
callOnAllChangesDone: _callOnAllChangesDone,
|
||||
changeDetection: _changeDetection,
|
||||
events: _events);
|
||||
|
||||
@override
|
||||
Object visitAnnotation(Annotation node) {
|
||||
var directiveType = _getDirectiveType('${node.name}', node.element);
|
||||
if (directiveType >= 0) {
|
||||
if (_hasMeta) {
|
||||
throw new FormatException(
|
||||
'Only one Directive is allowed per class. '
|
||||
'Found "$node" but already processed "$meta".',
|
||||
'$node' /* source */);
|
||||
}
|
||||
_initializeMetadata(directiveType);
|
||||
super.visitAnnotation(node);
|
||||
}
|
||||
// Annotation we do not recognize - no need to visit.
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
|
||||
var directiveType = _getDirectiveType(
|
||||
'${node.constructorName.type.name}', node.staticElement);
|
||||
if (directiveType >= 0) {
|
||||
if (_hasMeta) {
|
||||
throw new FormatException(
|
||||
'Only one Directive is allowed per class. '
|
||||
'Found "$node" but already processed "$meta".',
|
||||
'$node' /* source */);
|
||||
}
|
||||
_initializeMetadata(directiveType);
|
||||
super.visitInstanceCreationExpression(node);
|
||||
}
|
||||
// Annotation we do not recognize - no need to visit.
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitNamedExpression(NamedExpression node) {
|
||||
// TODO(kegluneq): Remove this limitation.
|
||||
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
|
||||
throw new FormatException(
|
||||
'Angular 2 currently only supports simple identifiers in directives.',
|
||||
'$node' /* source */);
|
||||
}
|
||||
var keyString = '${node.name.label}';
|
||||
switch (keyString) {
|
||||
case 'selector':
|
||||
_populateSelector(node.expression);
|
||||
break;
|
||||
case 'compileChildren':
|
||||
_populateCompileChildren(node.expression);
|
||||
break;
|
||||
case 'properties':
|
||||
_populateProperties(node.expression);
|
||||
break;
|
||||
case 'host':
|
||||
_populateHost(node.expression);
|
||||
break;
|
||||
case 'lifecycle':
|
||||
_populateLifecycle(node.expression);
|
||||
break;
|
||||
case 'exportAs':
|
||||
_populateExportAs(node.expression);
|
||||
break;
|
||||
case 'changeDetection':
|
||||
_populateChangeDetection(node.expression);
|
||||
break;
|
||||
case 'events':
|
||||
_populateEvents(node.expression);
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _expressionToString(Expression node, String nodeDescription) {
|
||||
var value = node.accept(_evaluator);
|
||||
if (value is! String) {
|
||||
throw new FormatException(
|
||||
'Angular 2 could not understand the value '
|
||||
'in $nodeDescription.',
|
||||
'$node' /* source */);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void _populateSelector(Expression selectorValue) {
|
||||
_checkMeta();
|
||||
_selector = _expressionToString(selectorValue, 'Directive#selector');
|
||||
}
|
||||
|
||||
void _checkMeta() {
|
||||
if (!_hasMeta) {
|
||||
throw new ArgumentError(
|
||||
'Incorrect value passed to readDirectiveMetadata. '
|
||||
'Expected types are Annotation, InstanceCreationExpression, '
|
||||
'NodeList or ListLiteral');
|
||||
}
|
||||
}
|
||||
|
||||
void _populateCompileChildren(Expression compileChildrenValue) {
|
||||
_checkMeta();
|
||||
var evaluated = compileChildrenValue.accept(_evaluator);
|
||||
if (evaluated is! bool) {
|
||||
throw new FormatException(
|
||||
'Angular 2 expects a bool but could not understand the value for '
|
||||
'Directive#compileChildren.',
|
||||
'$compileChildrenValue' /* source */);
|
||||
}
|
||||
_compileChildren = evaluated;
|
||||
}
|
||||
|
||||
/// Evaluates the [Map] represented by `expression` and adds all `key`,
|
||||
/// `value` pairs to `map`. If `expression` does not evaluate to a [Map],
|
||||
/// throws a descriptive [FormatException].
|
||||
void _populateMap(Expression expression, Map map, String propertyName) {
|
||||
var evaluated = expression.accept(_evaluator);
|
||||
if (evaluated is! Map) {
|
||||
throw new FormatException(
|
||||
'Angular 2 expects a Map but could not understand the value for '
|
||||
'$propertyName.',
|
||||
'$expression' /* source */);
|
||||
}
|
||||
evaluated.forEach((key, value) {
|
||||
if (value != null) {
|
||||
map[key] = '$value';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Evaluates the [List] represented by `expression` and adds all values,
|
||||
/// to `list`. If `expression` does not evaluate to a [List], throws a
|
||||
/// descriptive [FormatException].
|
||||
void _populateList(Expression expression, List list, String propertyName) {
|
||||
var evaluated = expression.accept(_evaluator);
|
||||
if (evaluated is! List) {
|
||||
throw new FormatException(
|
||||
'Angular 2 expects a List but could not understand the value for '
|
||||
'$propertyName.',
|
||||
'$expression' /* source */);
|
||||
}
|
||||
list.addAll(evaluated);
|
||||
}
|
||||
|
||||
void _populateProperties(Expression propertiesValue) {
|
||||
_checkMeta();
|
||||
_populateList(propertiesValue, _properties, 'Directive#properties');
|
||||
}
|
||||
|
||||
void _populateHost(Expression hostValue) {
|
||||
_checkMeta();
|
||||
_populateMap(hostValue, _host, 'Directive#host');
|
||||
}
|
||||
|
||||
void _populateExportAs(Expression exportAsValue) {
|
||||
_checkMeta();
|
||||
_exportAs = _expressionToString(exportAsValue, 'Directive#exportAs');
|
||||
}
|
||||
|
||||
void _populateLifecycle(Expression lifecycleValue) {
|
||||
_checkMeta();
|
||||
if (lifecycleValue is! ListLiteral) {
|
||||
throw new FormatException(
|
||||
'Angular 2 expects a List but could not understand the value for lifecycle. '
|
||||
'$lifecycleValue');
|
||||
}
|
||||
ListLiteral l = lifecycleValue;
|
||||
var lifecycleEvents = l.elements.map((s) => s.toSource().split('.').last);
|
||||
_callOnDestroy = lifecycleEvents.contains("onDestroy");
|
||||
_callOnChange = lifecycleEvents.contains("onChange");
|
||||
_callOnCheck = lifecycleEvents.contains("onCheck");
|
||||
_callOnInit = lifecycleEvents.contains("onInit");
|
||||
_callOnAllChangesDone = lifecycleEvents.contains("onAllChangesDone");
|
||||
}
|
||||
|
||||
void _populateEvents(Expression eventsValue) {
|
||||
_checkMeta();
|
||||
_populateList(eventsValue, _events, 'Directive#events');
|
||||
}
|
||||
|
||||
void _populateChangeDetection(Expression value) {
|
||||
_checkMeta();
|
||||
_changeDetection = _expressionToString(value, 'Directive#changeDetection');
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
library angular2.transform.common.formatter;
|
||||
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
||||
AngularDartFormatter _formatter = null;
|
||||
|
||||
void init(DartFormatter formatter) {
|
||||
_formatter = new _RealFormatter(formatter);
|
||||
}
|
||||
|
||||
AngularDartFormatter get formatter {
|
||||
if (_formatter == null) {
|
||||
_formatter = new _PassThroughFormatter();
|
||||
}
|
||||
return _formatter;
|
||||
}
|
||||
|
||||
abstract class AngularDartFormatter {
|
||||
String format(String source, {uri});
|
||||
}
|
||||
|
||||
class _PassThroughFormatter implements AngularDartFormatter {
|
||||
String format(String source, {uri}) => source;
|
||||
}
|
||||
|
||||
class _RealFormatter implements AngularDartFormatter {
|
||||
final DartFormatter _formatter;
|
||||
|
||||
_RealFormatter(this._formatter);
|
||||
|
||||
String format(source, {uri}) => _formatter.format(source, uri: uri);
|
||||
}
|
@ -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/metadata.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/metadata.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/metadata.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/metadata.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/metadata.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);
|
||||
}
|
102
modules_dart/transform/lib/src/transform/common/logging.dart
Normal file
102
modules_dart/transform/lib/src/transform/common/logging.dart
Normal file
@ -0,0 +1,102 @@
|
||||
library angular2.transform.common.logging;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/messages/build_logger.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
typedef _SimpleCallback();
|
||||
|
||||
// The key used to store the logger on the current zone.
|
||||
final _key = #loggingZonedLoggerKey;
|
||||
|
||||
/// Executes {@link fn} inside a new {@link Zone} with its own logger.
|
||||
Future<dynamic> initZoned(Transform t, _SimpleCallback fn) =>
|
||||
setZoned(new BuildLogger(t), fn);
|
||||
|
||||
Future<dynamic> setZoned(BuildLogger logger, _SimpleCallback fn) async {
|
||||
return runZoned(() async {
|
||||
try {
|
||||
await fn();
|
||||
} on AnalyzerError catch (e) {
|
||||
// Do not worry about printing the stack trace, barback will handle that
|
||||
// on its own when it catches the rethrown exception.
|
||||
logger
|
||||
.error(' Failed with ${e.runtimeType}\n${_friendlyError(e.error)}');
|
||||
rethrow;
|
||||
} on AnalyzerErrorGroup catch (eGroup) {
|
||||
// See above re: stack trace.
|
||||
var numErrors = eGroup.errors.length;
|
||||
if (numErrors == 1) {
|
||||
logger.error(_friendlyError(eGroup.errors[0].error));
|
||||
} else {
|
||||
var buf = new StringBuffer();
|
||||
buf.writeln(' Failed with ${numErrors} errors');
|
||||
for (var i = 0; i < numErrors; ++i) {
|
||||
buf.writeln(
|
||||
'Error ${i + 1}: ${_friendlyError(eGroup.errors[i].error)}');
|
||||
}
|
||||
logger.error('$buf');
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}, zoneValues: {_key: logger});
|
||||
}
|
||||
|
||||
/// The logger for the current {@link Zone}.
|
||||
BuildLogger get logger {
|
||||
var current = Zone.current[_key] as BuildLogger;
|
||||
return current == null ? new PrintLogger() : current;
|
||||
}
|
||||
|
||||
class PrintLogger implements BuildLogger {
|
||||
@override
|
||||
final String detailsUri = '';
|
||||
@override
|
||||
final bool convertErrorsToWarnings = false;
|
||||
|
||||
void _printWithPrefix(prefix, msg) => print('$prefix: $msg');
|
||||
void info(msg, {AssetId asset, SourceSpan span}) =>
|
||||
_printWithPrefix('INFO', msg);
|
||||
void fine(msg, {AssetId asset, SourceSpan span}) =>
|
||||
_printWithPrefix('FINE', msg);
|
||||
void warning(msg, {AssetId asset, SourceSpan span}) =>
|
||||
_printWithPrefix('WARN', msg);
|
||||
void error(msg, {AssetId asset, SourceSpan span}) {
|
||||
throw new PrintLoggerError(msg, asset, span);
|
||||
}
|
||||
|
||||
Future writeOutput() => null;
|
||||
Future addLogFilesFromAsset(AssetId id, [int nextNumber = 1]) => null;
|
||||
}
|
||||
|
||||
class PrintLoggerError extends Error {
|
||||
final String message;
|
||||
final AssetId asset;
|
||||
final SourceSpan span;
|
||||
|
||||
PrintLoggerError(this.message, this.asset, this.span);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Message: ${Error.safeToString(message)}, '
|
||||
'Asset: ${Error.safeToString(asset)}, '
|
||||
'Span: ${Error.safeToString(span)}.';
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a human-readable error message from `error`.
|
||||
String _friendlyError(AnalysisError error) {
|
||||
if (error.source != null) {
|
||||
var file =
|
||||
new SourceFile(error.source.contents.data, url: error.source.fullName);
|
||||
|
||||
return file
|
||||
.span(error.offset, error.offset + error.length)
|
||||
.message(error.message, color: false);
|
||||
} else {
|
||||
return '<unknown location>: ${error.message}';
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
library angular2.transform.common.mirror_mode;
|
||||
|
||||
/// Modes for mirror use.
|
||||
/// `none` is the default value and signifies that mirror use should be
|
||||
/// removed.
|
||||
/// `debug` allows the use of mirrors and logs a notice whenever they are
|
||||
/// accessed.
|
||||
/// `verbose` allows the use of mirrors and logs a stack trace whenever they
|
||||
/// are accessed.
|
||||
enum MirrorMode { debug, none, verbose }
|
43
modules_dart/transform/lib/src/transform/common/names.dart
Normal file
43
modules_dart/transform/lib/src/transform/common/names.dart
Normal file
@ -0,0 +1,43 @@
|
||||
library angular2.transform.common.names;
|
||||
|
||||
const BOOTSTRAP_NAME = 'bootstrap';
|
||||
const SETUP_METHOD_NAME = 'initReflector';
|
||||
const REFLECTOR_VAR_NAME = 'reflector';
|
||||
const TRANSFORM_DYNAMIC_MODE = 'transform_dynamic';
|
||||
const DEPS_EXTENSION = '.ng_deps.dart';
|
||||
const META_EXTENSION = '.ng_meta.json';
|
||||
// TODO(sigmund): consider merging into .ng_meta by generating local metadata
|
||||
// upfront (rather than extracting it from ng_deps).
|
||||
const ALIAS_EXTENSION = '.aliases.json';
|
||||
const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities';
|
||||
const REGISTER_TYPE_METHOD_NAME = 'registerType';
|
||||
const REGISTER_GETTERS_METHOD_NAME = 'registerGetters';
|
||||
const REGISTER_SETTERS_METHOD_NAME = 'registerSetters';
|
||||
const REGISTER_METHODS_METHOD_NAME = 'registerMethods';
|
||||
|
||||
/// Returns `uri` with its extension updated to [META_EXTENSION].
|
||||
String toMetaExtension(String uri) =>
|
||||
_toExtension(uri, const [DEPS_EXTENSION, '.dart'], META_EXTENSION);
|
||||
|
||||
/// Returns `uri` with its extension updated to [DEPS_EXTENSION].
|
||||
String toDepsExtension(String uri) =>
|
||||
_toExtension(uri, const [META_EXTENSION, '.dart'], DEPS_EXTENSION);
|
||||
|
||||
/// Returns `uri` with its extension updated to [ALIAS_EXTENSION].
|
||||
String toAliasExtension(String uri) =>
|
||||
_toExtension(uri, const [DEPS_EXTENSION, '.dart'], ALIAS_EXTENSION);
|
||||
|
||||
/// Returns `uri` with its extension updated to `toExtension` if its
|
||||
/// extension is currently in `fromExtension`.
|
||||
String _toExtension(
|
||||
String uri, Iterable<String> fromExtensions, String toExtension) {
|
||||
if (uri == null) return null;
|
||||
if (uri.endsWith(toExtension)) return uri;
|
||||
for (var extension in fromExtensions) {
|
||||
if (uri.endsWith(extension)) {
|
||||
return '${uri.substring(0, uri.length-extension.length)}'
|
||||
'$toExtension';
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
76
modules_dart/transform/lib/src/transform/common/ng_deps.dart
Normal file
76
modules_dart/transform/lib/src/transform/common/ng_deps.dart
Normal file
@ -0,0 +1,76 @@
|
||||
library angular2.transform.common.parser;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'registered_type.dart';
|
||||
|
||||
export 'registered_type.dart';
|
||||
|
||||
/// The contents of a `.ng_deps.dart` file.
|
||||
class NgDeps {
|
||||
final String code;
|
||||
final List<ImportDirective> imports = [];
|
||||
final List<ExportDirective> exports = [];
|
||||
final List<RegisteredType> registeredTypes = [];
|
||||
LibraryDirective lib = null;
|
||||
FunctionDeclaration setupMethod = null;
|
||||
|
||||
static _ParseNgDepsVisitor _visitor = new _ParseNgDepsVisitor();
|
||||
|
||||
/// Parses only the `.ng_deps.dart` file represented by `id`.
|
||||
static Future<NgDeps> parse(AssetReader reader, AssetId id) async {
|
||||
if (!(await reader.hasInput(id))) return null;
|
||||
var ngDeps = new NgDeps(await reader.readAsString(id));
|
||||
_visitor.ngDeps = ngDeps;
|
||||
parseCompilationUnit(ngDeps.code, name: id.path).accept(_visitor);
|
||||
return ngDeps;
|
||||
}
|
||||
|
||||
NgDeps(this.code);
|
||||
}
|
||||
|
||||
class _ParseNgDepsVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||
NgDeps ngDeps = null;
|
||||
|
||||
@override
|
||||
Object visitLibraryDirective(LibraryDirective node) {
|
||||
ngDeps.lib = node;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitImportDirective(ImportDirective node) {
|
||||
ngDeps.imports.add(node);
|
||||
return super.visitImportDirective(node);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitExportDirective(ExportDirective node) {
|
||||
ngDeps.exports.add(node);
|
||||
return super.visitExportDirective(node);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if ('${node.name}' == SETUP_METHOD_NAME) {
|
||||
ngDeps.setupMethod = node;
|
||||
}
|
||||
return super.visitFunctionDeclaration(node);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitMethodInvocation(MethodInvocation node) {
|
||||
var isRegisterType = '${node.methodName}' == REGISTER_TYPE_METHOD_NAME;
|
||||
|
||||
if (isRegisterType) {
|
||||
ngDeps.registeredTypes.add(new RegisteredType.fromMethodInvocation(node));
|
||||
}
|
||||
|
||||
return super.visitMethodInvocation(node);
|
||||
}
|
||||
}
|
88
modules_dart/transform/lib/src/transform/common/ng_meta.dart
Normal file
88
modules_dart/transform/lib/src/transform/common/ng_meta.dart
Normal file
@ -0,0 +1,88 @@
|
||||
library angular2.transform.common.ng_meta;
|
||||
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/render/dom/convert.dart';
|
||||
import 'logging.dart';
|
||||
|
||||
/// Metadata about directives and directive aliases.
|
||||
///
|
||||
/// [NgMeta] is used in three stages of the transformation process. First we
|
||||
/// store directive aliases exported from a single file in an [NgMeta] instance.
|
||||
/// Later we use another [NgMeta] instance to store more information about a
|
||||
/// single file, including both directive aliases and directives extracted from
|
||||
/// the corresponding `.ng_deps.dart` file. Further down the compilation
|
||||
/// process, the template compiler needs to reason about the namespace of import
|
||||
/// prefixes, so it will combine multple [NgMeta] instances together if they
|
||||
/// were imported into a file with the same prefix.
|
||||
///
|
||||
/// Instances of this class are serialized into `.aliases.json` and
|
||||
/// `.ng_meta.json` files as intermediate assets to make the compilation process
|
||||
/// easier.
|
||||
class NgMeta {
|
||||
/// Directive metadata for each type annotated as a directive.
|
||||
final Map<String, RenderDirectiveMetadata> types;
|
||||
|
||||
/// List of other types and names associated with a given name.
|
||||
final Map<String, List<String>> aliases;
|
||||
|
||||
NgMeta(this.types, this.aliases);
|
||||
|
||||
NgMeta.empty() : this({}, {});
|
||||
|
||||
bool get isEmpty => types.isEmpty && aliases.isEmpty;
|
||||
|
||||
/// Parse from the serialized form produced by [toJson].
|
||||
factory NgMeta.fromJson(Map json) {
|
||||
var types = {};
|
||||
var aliases = {};
|
||||
for (var key in json.keys) {
|
||||
var entry = json[key];
|
||||
if (entry['kind'] == 'type') {
|
||||
types[key] = directiveMetadataFromMap(entry['value']);
|
||||
} else if (entry['kind'] == 'alias') {
|
||||
aliases[key] = entry['value'];
|
||||
}
|
||||
}
|
||||
return new NgMeta(types, aliases);
|
||||
}
|
||||
|
||||
/// Serialized representation of this instance.
|
||||
Map toJson() {
|
||||
var result = {};
|
||||
types.forEach((k, v) {
|
||||
result[k] = {'kind': 'type', 'value': directiveMetadataToMap(v)};
|
||||
});
|
||||
|
||||
aliases.forEach((k, v) {
|
||||
result[k] = {'kind': 'alias', 'value': v};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Merge into this instance all information from [other].
|
||||
void addAll(NgMeta other) {
|
||||
types.addAll(other.types);
|
||||
aliases.addAll(other.aliases);
|
||||
}
|
||||
|
||||
/// Returns the metadata for every type associated with the given [alias].
|
||||
List<RenderDirectiveMetadata> flatten(String alias) {
|
||||
var result = [];
|
||||
var seen = new Set();
|
||||
helper(name) {
|
||||
if (!seen.add(name)) {
|
||||
logger.warning('Circular alias dependency for "$name".');
|
||||
return;
|
||||
}
|
||||
if (types.containsKey(name)) {
|
||||
result.add(types[name]);
|
||||
} else if (aliases.containsKey(name)) {
|
||||
aliases[name].forEach(helper);
|
||||
} else {
|
||||
logger.warning('Unknown alias: "$name".');
|
||||
}
|
||||
}
|
||||
helper(alias);
|
||||
return result;
|
||||
}
|
||||
}
|
106
modules_dart/transform/lib/src/transform/common/options.dart
Normal file
106
modules_dart/transform/lib/src/transform/common/options.dart
Normal file
@ -0,0 +1,106 @@
|
||||
library angular2.transform.common.options;
|
||||
|
||||
import 'annotation_matcher.dart';
|
||||
import 'mirror_mode.dart';
|
||||
|
||||
/// See `optimizationPhases` below for an explanation.
|
||||
const DEFAULT_OPTIMIZATION_PHASES = 5;
|
||||
|
||||
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
|
||||
const ENTRY_POINT_PARAM = 'entry_points';
|
||||
const FORMAT_CODE_PARAM = 'format_code';
|
||||
const GENERATE_CHANGE_DETECTORS_PARAM = 'generate_change_detectors';
|
||||
const REFLECT_PROPERTIES_AS_ATTRIBUTES = 'reflectPropertiesAsAttributes';
|
||||
const INIT_REFLECTOR_PARAM = 'init_reflector';
|
||||
const INLINE_VIEWS_PARAM = 'inline_views';
|
||||
const MIRROR_MODE_PARAM = 'mirror_mode';
|
||||
const OPTIMIZATION_PHASES_PARAM = 'optimization_phases';
|
||||
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
|
||||
|
||||
/// Provides information necessary to transform an Angular2 app.
|
||||
class TransformerOptions {
|
||||
/// The path to the files where the application's calls to `bootstrap` are.
|
||||
final List<String> entryPoints;
|
||||
|
||||
/// The paths to the files where the application's {@link ReflectionCapabilities}
|
||||
/// are set.
|
||||
final List<String> reflectionEntryPoints;
|
||||
|
||||
/// The `BarbackMode#name` we are running in.
|
||||
final String modeName;
|
||||
|
||||
/// The [MirrorMode] to use for the transformation.
|
||||
final MirrorMode mirrorMode;
|
||||
|
||||
/// Whether to generate calls to our generated `initReflector` code
|
||||
final bool initReflector;
|
||||
|
||||
/// Whether to inline html/css urls into the View directive
|
||||
final bool inlineViews;
|
||||
|
||||
/// The [AnnotationMatcher] which is used to identify angular annotations.
|
||||
final AnnotationMatcher annotationMatcher;
|
||||
|
||||
/// Whether to create change detector classes for discovered `@View`s.
|
||||
final bool generateChangeDetectors;
|
||||
|
||||
final bool reflectPropertiesAsAttributes;
|
||||
|
||||
/// The number of phases to spend optimizing output size.
|
||||
/// Each additional phase adds time to the transformation but may decrease
|
||||
/// final output size. There is a limit beyond which this will no longer
|
||||
/// decrease size, that is, setting this to 20 may not decrease size any
|
||||
/// more than setting it to 10, but you will still pay an additional
|
||||
/// penalty in transformation time.
|
||||
/// The "correct" number of phases varies with the structure of the app.
|
||||
final int optimizationPhases;
|
||||
|
||||
/// Whether to format generated code.
|
||||
/// Code that is only modified will never be formatted because doing so may
|
||||
/// invalidate the source maps generated by `dart2js` and/or other tools.
|
||||
final bool formatCode;
|
||||
|
||||
TransformerOptions._internal(
|
||||
this.entryPoints,
|
||||
this.reflectionEntryPoints,
|
||||
this.modeName,
|
||||
this.mirrorMode,
|
||||
this.initReflector,
|
||||
this.annotationMatcher,
|
||||
this.optimizationPhases,
|
||||
{this.generateChangeDetectors,
|
||||
this.reflectPropertiesAsAttributes,
|
||||
this.inlineViews,
|
||||
this.formatCode});
|
||||
|
||||
factory TransformerOptions(List<String> entryPoints,
|
||||
{List<String> reflectionEntryPoints,
|
||||
String modeName: 'release',
|
||||
MirrorMode mirrorMode: MirrorMode.none,
|
||||
bool initReflector: true,
|
||||
List<ClassDescriptor> customAnnotationDescriptors: const [],
|
||||
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES,
|
||||
bool inlineViews: true,
|
||||
bool generateChangeDetectors: true,
|
||||
bool reflectPropertiesAsAttributes: true,
|
||||
bool formatCode: false}) {
|
||||
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
|
||||
reflectionEntryPoints = entryPoints;
|
||||
}
|
||||
var annotationMatcher = new AnnotationMatcher()
|
||||
..addAll(customAnnotationDescriptors);
|
||||
optimizationPhases = optimizationPhases.isNegative ? 0 : optimizationPhases;
|
||||
return new TransformerOptions._internal(
|
||||
entryPoints,
|
||||
reflectionEntryPoints,
|
||||
modeName,
|
||||
mirrorMode,
|
||||
initReflector,
|
||||
annotationMatcher,
|
||||
optimizationPhases,
|
||||
generateChangeDetectors: generateChangeDetectors,
|
||||
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
|
||||
inlineViews: inlineViews,
|
||||
formatCode: formatCode);
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
library angular2.transform.common.options_reader;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'annotation_matcher.dart';
|
||||
import 'mirror_mode.dart';
|
||||
import 'options.dart';
|
||||
|
||||
TransformerOptions parseBarbackSettings(BarbackSettings settings) {
|
||||
var config = settings.configuration;
|
||||
var entryPoints = _readFileList(config, ENTRY_POINT_PARAM);
|
||||
var reflectionEntryPoints =
|
||||
_readFileList(config, REFLECTION_ENTRY_POINT_PARAM);
|
||||
var initReflector =
|
||||
_readBool(config, INIT_REFLECTOR_PARAM, defaultValue: true);
|
||||
var inlineViews = _readBool(config, INLINE_VIEWS_PARAM, defaultValue: true);
|
||||
var generateChangeDetectors =
|
||||
_readBool(config, GENERATE_CHANGE_DETECTORS_PARAM, defaultValue: true);
|
||||
var reflectPropertiesAsAttributes =
|
||||
_readBool(config, REFLECT_PROPERTIES_AS_ATTRIBUTES, defaultValue: false);
|
||||
var formatCode = _readBool(config, FORMAT_CODE_PARAM, defaultValue: false);
|
||||
String mirrorModeVal =
|
||||
config.containsKey(MIRROR_MODE_PARAM) ? config[MIRROR_MODE_PARAM] : '';
|
||||
var mirrorMode = MirrorMode.none;
|
||||
switch (mirrorModeVal) {
|
||||
case 'debug':
|
||||
mirrorMode = MirrorMode.debug;
|
||||
break;
|
||||
case 'verbose':
|
||||
mirrorMode = MirrorMode.verbose;
|
||||
break;
|
||||
default:
|
||||
mirrorMode = MirrorMode.none;
|
||||
break;
|
||||
}
|
||||
var optimizationPhases = _readInt(config, OPTIMIZATION_PHASES_PARAM,
|
||||
defaultValue: DEFAULT_OPTIMIZATION_PHASES);
|
||||
return new TransformerOptions(entryPoints,
|
||||
reflectionEntryPoints: reflectionEntryPoints,
|
||||
modeName: settings.mode.name,
|
||||
mirrorMode: mirrorMode,
|
||||
initReflector: initReflector,
|
||||
inlineViews: inlineViews,
|
||||
customAnnotationDescriptors: _readCustomAnnotations(config),
|
||||
optimizationPhases: optimizationPhases,
|
||||
generateChangeDetectors: generateChangeDetectors,
|
||||
reflectPropertiesAsAttributes: reflectPropertiesAsAttributes,
|
||||
formatCode: formatCode);
|
||||
}
|
||||
|
||||
bool _readBool(Map config, String paramName, {bool defaultValue}) {
|
||||
return config.containsKey(paramName)
|
||||
? config[paramName] != false
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
/// Cribbed from the polymer project.
|
||||
/// {@link https://github.com/dart-lang/polymer-dart}
|
||||
List<String> _readFileList(Map config, String paramName) {
|
||||
var value = config[paramName];
|
||||
if (value == null) return null;
|
||||
var files = [];
|
||||
bool error = false;
|
||||
if (value is List) {
|
||||
files = value;
|
||||
error = value.any((e) => e is! String);
|
||||
} else if (value is String) {
|
||||
files = [value];
|
||||
error = false;
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
if (error) {
|
||||
print('Invalid value for "$paramName" in the Angular 2 transformer.');
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
int _readInt(Map config, String paramName, {int defaultValue: null}) {
|
||||
if (!config.containsKey(paramName)) return defaultValue;
|
||||
var value = config[paramName];
|
||||
if (value is String) {
|
||||
value = int.parse(value);
|
||||
}
|
||||
if (value is! int) {
|
||||
throw new ArgumentError.value(value, paramName, 'Expected an integer');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into
|
||||
/// [ClassDescriptor]s.
|
||||
List<ClassDescriptor> _readCustomAnnotations(Map config) {
|
||||
var descriptors = [];
|
||||
var customAnnotations = config[CUSTOM_ANNOTATIONS_PARAM];
|
||||
if (customAnnotations == null) return descriptors;
|
||||
var error = false;
|
||||
if (customAnnotations is! List) {
|
||||
error = true;
|
||||
} else {
|
||||
for (var description in customAnnotations) {
|
||||
if (description is! Map) {
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
var name = description['name'];
|
||||
var import = description['import'];
|
||||
var superClass = description['superClass'];
|
||||
if (name == null || import == null || superClass == null) {
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
descriptors
|
||||
.add(new ClassDescriptor(name, import, superClass: superClass));
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
print(CUSTOM_ANNOTATIONS_ERROR);
|
||||
}
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
const CUSTOM_ANNOTATIONS_ERROR = '''
|
||||
Invalid value for $CUSTOM_ANNOTATIONS_PARAM in the Angular2 transformer.
|
||||
Expected something that looks like the following:
|
||||
|
||||
transformers:
|
||||
- angular2:
|
||||
custom_annotations:
|
||||
- name: MyAnnotation
|
||||
import: 'package:my_package/my_annotation.dart'
|
||||
superClass: Component
|
||||
- name: ...
|
||||
import: ...
|
||||
superClass: ...''';
|
@ -0,0 +1,36 @@
|
||||
library angular2.transform.common.property_utils;
|
||||
|
||||
import 'package:analyzer/src/generated/scanner.dart' show Keyword;
|
||||
|
||||
/// Whether `name` is a valid property name.
|
||||
bool isValid(String name) {
|
||||
var keyword = Keyword.keywords[name];
|
||||
return keyword == null || keyword.isPseudoKeyword;
|
||||
}
|
||||
|
||||
/// Prepares `name` to be emitted inside a string.
|
||||
String sanitize(String name) => name.replaceAll('\$', '\\\$');
|
||||
|
||||
/// Get a string usable as a lazy invalid setter, that is, one which will
|
||||
/// `throw` immediately upon use.
|
||||
String lazyInvalidSetter(String setterName) {
|
||||
var sName = sanitize(setterName);
|
||||
return ''' '$sName': (o, v) => '''
|
||||
''' throw 'Invalid setter name "$sName" is a Dart keyword.' ''';
|
||||
}
|
||||
|
||||
/// Get a string usable as a lazy invalid getter, that is, one which will
|
||||
/// `throw` immediately upon use.
|
||||
String lazyInvalidGetter(String getterName) {
|
||||
var sName = sanitize(getterName);
|
||||
return ''' '$sName': (o) => '''
|
||||
''' throw 'Invalid getter name "$sName" is a Dart keyword.' ''';
|
||||
}
|
||||
|
||||
/// Get a string usable as a lazy invalid method, that is, one which will
|
||||
/// `throw` immediately upon use.
|
||||
String lazyInvalidMethod(String methodName) {
|
||||
var sName = sanitize(methodName);
|
||||
return ''' '$sName': (o, args) => '''
|
||||
''' throw 'Invalid method name "$sName" is a Dart keyword.' ''';
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
library angular2.transform.common.registered_type;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
|
||||
/// A call to `Reflector#registerType` generated by `DirectiveProcessor`.
|
||||
class RegisteredType {
|
||||
/// The type registered by this call.
|
||||
final Identifier typeName;
|
||||
|
||||
/// The actual call to `Reflector#registerType`.
|
||||
final MethodInvocation registerMethod;
|
||||
|
||||
/// The factory method registered.
|
||||
final Expression factoryFn;
|
||||
|
||||
/// The parameters registered.
|
||||
final Expression parameters;
|
||||
|
||||
/// The annotations registered.
|
||||
final Expression annotations;
|
||||
|
||||
RenderDirectiveMetadata _directiveMetadata = null;
|
||||
|
||||
RegisteredType._(this.typeName, this.registerMethod, this.factoryFn,
|
||||
this.parameters, this.annotations);
|
||||
|
||||
/// Creates a {@link RegisteredType} given a {@link MethodInvocation} node representing
|
||||
/// a call to `registerType`.
|
||||
factory RegisteredType.fromMethodInvocation(MethodInvocation registerMethod) {
|
||||
var visitor = new _ParseRegisterTypeVisitor();
|
||||
registerMethod.accept(visitor);
|
||||
return new RegisteredType._(visitor.typeName, registerMethod,
|
||||
visitor.factoryFn, visitor.parameters, visitor.annotations);
|
||||
}
|
||||
|
||||
RenderDirectiveMetadata get directiveMetadata {
|
||||
if (_directiveMetadata == null) {
|
||||
try {
|
||||
_directiveMetadata = readDirectiveMetadata(annotations);
|
||||
if (_directiveMetadata != null) {
|
||||
_directiveMetadata.id = '$typeName';
|
||||
}
|
||||
} on FormatException catch (ex) {
|
||||
logger.error(ex.message);
|
||||
}
|
||||
}
|
||||
return _directiveMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
class _ParseRegisterTypeVisitor extends Object
|
||||
with RecursiveAstVisitor<Object> {
|
||||
Identifier typeName;
|
||||
Expression factoryFn;
|
||||
Expression parameters;
|
||||
Expression annotations;
|
||||
|
||||
@override
|
||||
Object visitMethodInvocation(MethodInvocation node) {
|
||||
assert('${node.methodName}' == REGISTER_TYPE_METHOD_NAME);
|
||||
|
||||
// The first argument to a `registerType` call is the type.
|
||||
typeName = node.argumentList.arguments[0] is Identifier
|
||||
? node.argumentList.arguments[0]
|
||||
: null;
|
||||
|
||||
// The second argument to a `registerType` call is the RegistrationInfo
|
||||
// object creation.
|
||||
var info = node.argumentList.arguments[1] as InstanceCreationExpression;
|
||||
var args = info.argumentList.arguments;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
var arg = args[i];
|
||||
if (i == 0) {
|
||||
annotations = arg;
|
||||
} else if (i == 1) {
|
||||
parameters = arg;
|
||||
} else if (i == 2) {
|
||||
factoryFn = arg;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
library angular2.transform.template_compiler.xhr_impl;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/render/xhr.dart' show XHR;
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/assets.dart';
|
||||
|
||||
class XhrImpl implements XHR {
|
||||
final AssetReader _reader;
|
||||
final AssetId _entryPoint;
|
||||
|
||||
XhrImpl(this._reader, this._entryPoint);
|
||||
|
||||
Future<String> get(String url) async {
|
||||
var assetId = uriToAssetId(_entryPoint, url, logger, null /* span */,
|
||||
errorOnAbsolute: false);
|
||||
if (assetId == null) {
|
||||
logger.error(
|
||||
'Uri $url not supported from $_entryPoint, could not build AssetId');
|
||||
return null;
|
||||
}
|
||||
var templateExists = await _reader.hasInput(assetId);
|
||||
if (!templateExists) {
|
||||
logger.error('Could not read asset at uri $url from $_entryPoint');
|
||||
return null;
|
||||
}
|
||||
return await _reader.readAsString(assetId);
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
library angular2.transform.deferred_rewriter.rewriter;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/assets.dart';
|
||||
import 'package:quiver/iterables.dart' as it;
|
||||
|
||||
class Rewriter {
|
||||
final AssetId _entryPoint;
|
||||
final AssetReader _reader;
|
||||
|
||||
Rewriter(this._entryPoint, this._reader);
|
||||
|
||||
/// Rewrites the provided code by finding all the deferred library imports
|
||||
/// and loadLibrary invocations. Then it removes any libraries that don't
|
||||
/// require angular codegen. For the remaining libraries it rewrites the
|
||||
/// import to the corresponding ng_dep file, and chains a future which
|
||||
/// first initializes the library.
|
||||
///
|
||||
/// To the extent possible, this method does not change line numbers or
|
||||
/// offsets in the provided code to facilitate debugging via source maps.
|
||||
Future<String> rewrite() async {
|
||||
var code = await _reader.readAsString(_entryPoint);
|
||||
var node = parseCompilationUnit(code);
|
||||
if (node == null) return null;
|
||||
|
||||
var visitor = new _FindDeferredLibraries(_reader, _entryPoint);
|
||||
node.accept(visitor);
|
||||
// Look to see if we found any deferred libraries
|
||||
if (!visitor.hasDeferredLibrariesToRewrite()) return null;
|
||||
// Remove any libraries that don't need angular codegen.
|
||||
await visitor.cull();
|
||||
// Check again if there are any deferred libraries.
|
||||
if (!visitor.hasDeferredLibrariesToRewrite()) return null;
|
||||
|
||||
var compare = (AstNode a, AstNode b) => a.offset - b.offset;
|
||||
visitor.deferredImports.sort(compare);
|
||||
visitor.loadLibraryInvocations.sort(compare);
|
||||
|
||||
var buf = new StringBuffer();
|
||||
var idx =
|
||||
visitor.deferredImports.fold(0, (int lastIdx, ImportDirective node) {
|
||||
buf.write(code.substring(lastIdx, node.offset));
|
||||
|
||||
var import = code.substring(node.offset, node.end);
|
||||
buf.write(import.replaceFirst('.dart', DEPS_EXTENSION));
|
||||
return node.end;
|
||||
});
|
||||
|
||||
idx = visitor.loadLibraryInvocations.fold(idx,
|
||||
(int lastIdx, MethodInvocation node) {
|
||||
buf.write(code.substring(lastIdx, node.offset));
|
||||
var value = node.realTarget as SimpleIdentifier;
|
||||
var prefix = value.name;
|
||||
// Chain a future that initializes the reflector.
|
||||
buf.write('$prefix.loadLibrary().then((_) {$prefix.initReflector();})');
|
||||
return node.end;
|
||||
});
|
||||
if (idx < code.length) buf.write(code.substring(idx));
|
||||
return '$buf';
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor responsible for finding the deferred libraries that need angular
|
||||
/// codegen. Those are the libraries that are loaded deferred and have a
|
||||
/// corresponding ng_deps file.
|
||||
class _FindDeferredLibraries extends Object with RecursiveAstVisitor<Object> {
|
||||
var deferredImports = new List<ImportDirective>();
|
||||
var loadLibraryInvocations = new List<MethodInvocation>();
|
||||
final AssetReader _reader;
|
||||
final AssetId _entryPoint;
|
||||
|
||||
_FindDeferredLibraries(this._reader, this._entryPoint);
|
||||
|
||||
@override
|
||||
Object visitImportDirective(ImportDirective node) {
|
||||
if (node.deferredKeyword != null) {
|
||||
deferredImports.add(node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitMethodInvocation(MethodInvocation node) {
|
||||
if (node.methodName.name == 'loadLibrary') {
|
||||
loadLibraryInvocations.add(node);
|
||||
}
|
||||
return super.visitMethodInvocation(node);
|
||||
}
|
||||
|
||||
bool hasDeferredLibrariesToRewrite() {
|
||||
if (deferredImports.isEmpty) {
|
||||
logger.fine('There are no deferred library imports.');
|
||||
return false;
|
||||
}
|
||||
if (loadLibraryInvocations.isEmpty) {
|
||||
logger.fine(
|
||||
'There are no loadLibrary invocations that need to be rewritten.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove all deferredImports that do not have a ng_dep file
|
||||
// then remove all loadLibrary invocations that are not in the set of
|
||||
// prefixes that are left.
|
||||
Future cull() async {
|
||||
// Determine whether a deferred import has ng_deps.
|
||||
var hasInputs = await Future.wait(deferredImports
|
||||
.map((import) => stringLiteralToString(import.uri))
|
||||
.map((uri) => toDepsExtension(uri))
|
||||
.map((depsUri) => uriToAssetId(_entryPoint, depsUri, logger, null,
|
||||
errorOnAbsolute: false))
|
||||
.map((asset) => _reader.hasInput(asset)));
|
||||
|
||||
// Filter out any deferred imports that do not have ng_deps.
|
||||
deferredImports = it.zip([deferredImports, hasInputs])
|
||||
.where((importHasInput) => importHasInput[1])
|
||||
.map((importHasInput) => importHasInput[0])
|
||||
.toList();
|
||||
|
||||
// Find the set of prefixes which have ng_deps.
|
||||
var prefixes =
|
||||
new Set.from(deferredImports.map((import) => import.prefix.name));
|
||||
|
||||
// Filters out any load library invocations where the prefix is not a known
|
||||
// library with ng_deps.
|
||||
loadLibraryInvocations = loadLibraryInvocations.where((library) {
|
||||
var value = library.realTarget as SimpleIdentifier;
|
||||
return prefixes.contains(value.name);
|
||||
}).toList();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
library angular2.transform.deferred_rewriter.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/options.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'rewriter.dart';
|
||||
|
||||
/// Transformer responsible for rewriting deferred library loads to enable
|
||||
/// initializing the reflector in a deferred way to keep the code with the
|
||||
/// deferred library.
|
||||
class DeferredRewriter extends Transformer {
|
||||
final TransformerOptions options;
|
||||
|
||||
DeferredRewriter(this.options);
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) =>
|
||||
id.extension.endsWith('dart') && !id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
var asset = transform.primaryInput;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var transformedCode = await rewriteDeferredLibraries(reader, asset.id);
|
||||
if (transformedCode != null) {
|
||||
transform.addOutput(
|
||||
new Asset.fromString(transform.primaryInput.id, transformedCode));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) async {
|
||||
var rewriter = new Rewriter(id, reader);
|
||||
return await rewriter.rewrite();
|
||||
}
|
36
modules_dart/transform/lib/src/transform/di_transformer.dart
Normal file
36
modules_dart/transform/lib/src/transform/di_transformer.dart
Normal file
@ -0,0 +1,36 @@
|
||||
library angular2.src.transform.di_transformer;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
||||
import 'directive_linker/transformer.dart';
|
||||
import 'directive_processor/transformer.dart';
|
||||
import 'reflection_remover/transformer.dart';
|
||||
import 'common/formatter.dart' as formatter;
|
||||
import 'common/options.dart';
|
||||
import 'common/options_reader.dart';
|
||||
|
||||
export 'common/options.dart';
|
||||
|
||||
/// Removes the mirror-based initialization logic and replaces it with static
|
||||
/// logic.
|
||||
class DiTransformerGroup extends TransformerGroup {
|
||||
DiTransformerGroup._(phases) : super(phases) {
|
||||
formatter.init(new DartFormatter());
|
||||
}
|
||||
|
||||
factory DiTransformerGroup(TransformerOptions options) {
|
||||
var phases = [
|
||||
[new ReflectionRemover(options)],
|
||||
[new DirectiveProcessor(null)]
|
||||
];
|
||||
phases.addAll(new List.generate(
|
||||
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
|
||||
phases.add([new DirectiveLinker()]);
|
||||
return new DiTransformerGroup._(phases);
|
||||
}
|
||||
|
||||
factory DiTransformerGroup.asPlugin(BarbackSettings settings) {
|
||||
return new DiTransformerGroup(parseBarbackSettings(settings));
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
library angular2.transform.directive_linker.linker;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/ng_deps.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/assets.dart';
|
||||
|
||||
/// Checks the `.ng_deps.dart` file represented by `entryPoint` and
|
||||
/// determines whether it is necessary to the functioning of the Angular 2
|
||||
/// Dart app.
|
||||
///
|
||||
/// An `.ng_deps.dart` file is not necessary if:
|
||||
/// 1. It does not register any `@Injectable` types with the system.
|
||||
/// 2. It does not import any libraries whose `.ng_deps.dart` files register
|
||||
/// any `@Injectable` types with the system.
|
||||
///
|
||||
/// Since `@Directive` and `@Component` inherit from `@Injectable`, we know
|
||||
/// we will not miss processing any classes annotated with those tags.
|
||||
Future<bool> isNecessary(AssetReader reader, AssetId entryPoint) async {
|
||||
NgDeps ngDeps = await NgDeps.parse(reader, entryPoint);
|
||||
|
||||
if (ngDeps.registeredTypes.isNotEmpty) return true;
|
||||
|
||||
// We do not register any @Injectables, do we call any dependencies?
|
||||
var linkedDepsMap =
|
||||
await _processNgImports(reader, entryPoint, _getSortedDeps(ngDeps));
|
||||
return linkedDepsMap.isNotEmpty;
|
||||
}
|
||||
|
||||
/// Modifies the `.ng_deps.dart` file represented by `entryPoint` to call its
|
||||
/// dependencies associated `initReflector` methods.
|
||||
///
|
||||
/// For example, if entry_point.ng_deps.dart imports dependency.dart, this
|
||||
/// will check if dependency.ng_deps.dart exists. If it does, we add:
|
||||
///
|
||||
/// ```
|
||||
/// import 'dependency.ng_deps.dart' as i0;
|
||||
/// ...
|
||||
/// void setupReflection(reflector) {
|
||||
/// ...
|
||||
/// i0.initReflector(reflector);
|
||||
/// }
|
||||
/// ```
|
||||
Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
|
||||
NgDeps ngDeps = await NgDeps.parse(reader, entryPoint);
|
||||
|
||||
if (ngDeps == null) return null;
|
||||
|
||||
var allDeps = _getSortedDeps(ngDeps);
|
||||
var linkedDepsMap = await _processNgImports(reader, entryPoint, allDeps);
|
||||
|
||||
if (linkedDepsMap.isEmpty) {
|
||||
// We are not calling `initReflector` on any other libraries.
|
||||
return ngDeps.code;
|
||||
}
|
||||
|
||||
var importBuf = new StringBuffer();
|
||||
var declarationBuf = new StringBuffer();
|
||||
var code = ngDeps.code;
|
||||
var codeIdx = 0;
|
||||
// Generate import statements for linked deps where necessary.
|
||||
for (var i = 0, it = allDeps.iterator; it.moveNext();) {
|
||||
if (linkedDepsMap.containsKey(it.current)) {
|
||||
importBuf.write(code.substring(codeIdx, it.current.end));
|
||||
codeIdx = it.current.end;
|
||||
importBuf.write('''
|
||||
import '${linkedDepsMap[it.current]}' as i${i};
|
||||
''');
|
||||
declarationBuf.write('i${i}.${SETUP_METHOD_NAME}();');
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
var declarationSeamIdx = ngDeps.setupMethod.end - 1;
|
||||
return '$importBuf'
|
||||
'${code.substring(codeIdx, declarationSeamIdx)}'
|
||||
'$declarationBuf'
|
||||
'${code.substring(declarationSeamIdx)}';
|
||||
}
|
||||
|
||||
/// All `import`s and `export`s in `ngDeps` sorted by order of appearance in
|
||||
/// the file.
|
||||
List<UriBasedDirective> _getSortedDeps(NgDeps ngDeps) {
|
||||
return <UriBasedDirective>[]
|
||||
..addAll(ngDeps.imports)
|
||||
..addAll(ngDeps.exports)
|
||||
..sort((a, b) => a.end.compareTo(b.end));
|
||||
}
|
||||
|
||||
bool _isNotDartDirective(UriBasedDirective directive) {
|
||||
return !stringLiteralToString(directive.uri).startsWith('dart:');
|
||||
}
|
||||
|
||||
/// Maps each input {@link UriBasedDirective} to its associated `.ng_deps.dart`
|
||||
/// file, if it exists.
|
||||
Future<Map<UriBasedDirective, String>> _processNgImports(AssetReader reader,
|
||||
AssetId entryPoint, Iterable<UriBasedDirective> directives) {
|
||||
final nullFuture = new Future.value(null);
|
||||
final retVal = <UriBasedDirective, String>{};
|
||||
return Future
|
||||
.wait(directives
|
||||
.where(_isNotDartDirective)
|
||||
.map((UriBasedDirective directive) {
|
||||
var ngDepsUri = toDepsExtension(stringLiteralToString(directive.uri));
|
||||
var spanArg = null;
|
||||
var ngDepsAsset = uriToAssetId(entryPoint, ngDepsUri, logger, spanArg,
|
||||
errorOnAbsolute: false);
|
||||
if (ngDepsAsset == entryPoint) return nullFuture;
|
||||
return reader.hasInput(ngDepsAsset).then((hasInput) {
|
||||
if (hasInput) {
|
||||
retVal[directive] = ngDepsUri;
|
||||
}
|
||||
}, onError: (_) => null);
|
||||
}))
|
||||
.then((_) => retVal);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
library angular2.transform.directive_linker.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'linker.dart';
|
||||
|
||||
/// Transformer responsible for processing .ng_deps.dart files created by
|
||||
/// {@link DirectiveProcessor} and ensuring that the generated calls to
|
||||
/// `setupReflection` call the necessary `setupReflection` method in all
|
||||
/// dependencies.
|
||||
class DirectiveLinker extends Transformer {
|
||||
DirectiveLinker();
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var assetId = transform.primaryInput.id;
|
||||
var assetPath = assetId.path;
|
||||
var transformedCode = await linkNgDeps(reader, assetId);
|
||||
if (transformedCode != null) {
|
||||
var formattedCode = formatter.format(transformedCode, uri: assetPath);
|
||||
transform.addOutput(new Asset.fromString(assetId, formattedCode));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformer responsible for removing unnecessary `.ng_deps.dart` files
|
||||
/// created by {@link DirectiveProcessor}.
|
||||
class EmptyNgDepsRemover extends Transformer {
|
||||
EmptyNgDepsRemover();
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
if (!(await isNecessary(reader, transform.primaryInput.id))) {
|
||||
transform.consumePrimary();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
library angular2.transform.directive_metadata_extractor.extractor;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/ng_deps.dart';
|
||||
import 'package:angular2/src/transform/common/ng_meta.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/assets.dart';
|
||||
|
||||
/// Returns [NgMeta] associated with [entryPoint].
|
||||
///
|
||||
/// This includes entries for every `Directive`-annotated classes and
|
||||
/// constants that match the directive-aliases pattern, which are visible in a
|
||||
/// file importing `entryPoint`. That is, this includes all `Directive`
|
||||
/// annotated public classes in `entryPoint`, all `DirectiveAlias` annotated
|
||||
/// public variables, and any assets which it `export`s. Returns an empty
|
||||
/// [NgMeta] if there are no `Directive`-annotated classes in `entryPoint`.
|
||||
Future<NgMeta> extractDirectiveMetadata(
|
||||
AssetReader reader, AssetId entryPoint) {
|
||||
return _extractDirectiveMetadataRecursive(reader, entryPoint);
|
||||
}
|
||||
|
||||
var _nullFuture = new Future.value(null);
|
||||
|
||||
Future<NgMeta> _extractDirectiveMetadataRecursive(
|
||||
AssetReader reader, AssetId entryPoint) async {
|
||||
var ngMeta = new NgMeta.empty();
|
||||
if (!(await reader.hasInput(entryPoint))) return ngMeta;
|
||||
|
||||
var ngDeps = await NgDeps.parse(reader, entryPoint);
|
||||
_extractMetadata(ngDeps, ngMeta);
|
||||
|
||||
var aliasesFile =
|
||||
new AssetId(entryPoint.package, toAliasExtension(entryPoint.path));
|
||||
|
||||
if (await reader.hasInput(aliasesFile)) {
|
||||
ngMeta.addAll(new NgMeta.fromJson(
|
||||
JSON.decode(await reader.readAsString(aliasesFile))));
|
||||
}
|
||||
|
||||
await Future.wait(ngDeps.exports.map((export) {
|
||||
var uri = stringLiteralToString(export.uri);
|
||||
if (uri.startsWith('dart:')) return _nullFuture;
|
||||
|
||||
uri = toDepsExtension(uri);
|
||||
var assetId = uriToAssetId(entryPoint, uri, logger, null /* span */,
|
||||
errorOnAbsolute: false);
|
||||
if (assetId == entryPoint) return _nullFuture;
|
||||
return _extractDirectiveMetadataRecursive(reader, assetId)
|
||||
.then(ngMeta.addAll);
|
||||
}));
|
||||
return ngMeta;
|
||||
}
|
||||
|
||||
// TODO(sigmund): rather than having to parse it from generated code. we should
|
||||
// be able to produce directly all information we need for ngMeta.
|
||||
void _extractMetadata(NgDeps ngDeps, NgMeta ngMeta) {
|
||||
if (ngDeps == null) return;
|
||||
ngDeps.registeredTypes.forEach((type) {
|
||||
ngMeta.types[type.typeName.name] = type.directiveMetadata;
|
||||
});
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
library angular2.transform.directive_metadata_extractor.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'extractor.dart';
|
||||
|
||||
/// Transformer responsible for processing .ng_deps.dart files created by
|
||||
/// {@link DirectiveProcessor} and creating associated `.ng_meta.dart` files.
|
||||
/// These files contain commented Json-formatted representations of all
|
||||
/// `Directive`s in the associated file.
|
||||
class DirectiveMetadataExtractor extends Transformer {
|
||||
final _encoder = const JsonEncoder.withIndent(' ');
|
||||
|
||||
DirectiveMetadataExtractor();
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var fromAssetId = transform.primaryInput.id;
|
||||
|
||||
var ngMeta = await extractDirectiveMetadata(reader, fromAssetId);
|
||||
if (ngMeta != null && !ngMeta.isEmpty) {
|
||||
transform.addOutput(new Asset.fromString(
|
||||
_outputAssetId(fromAssetId), _encoder.convert(ngMeta.toJson())));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AssetId _outputAssetId(AssetId inputAssetId) {
|
||||
assert(inputAssetId.path.endsWith(DEPS_EXTENSION));
|
||||
return new AssetId(inputAssetId.package, toMetaExtension(inputAssetId.path));
|
||||
}
|
@ -0,0 +1,449 @@
|
||||
library angular2.transform.directive_processor.rewriter;
|
||||
|
||||
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/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';
|
||||
import 'package:angular2/src/transform/common/ng_meta.dart';
|
||||
import 'package:barback/barback.dart' show AssetId;
|
||||
import 'package:code_transformers/assets.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import 'visitors.dart';
|
||||
|
||||
/// Generates a file registering all Angular 2 `Directive`s found in `code` in
|
||||
/// ngDeps format [TODO(kegluneq): documentation reference needed]. `assetId` is
|
||||
/// the id of the asset containing `code`.
|
||||
///
|
||||
/// 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, NgMeta ngMeta,
|
||||
{bool inlineViews}) async {
|
||||
// TODO(kegluneq): Shortcut if we can determine that there are no
|
||||
// [Directive]s present, taking into account `export`s.
|
||||
var code = await reader.readAsString(assetId);
|
||||
|
||||
var directivesVisitor = new _NgDepsDirectivesVisitor();
|
||||
parseDirectives(code, name: assetId.path)
|
||||
.directives
|
||||
.accept(directivesVisitor);
|
||||
|
||||
// If this is part of another library, its contents will be processed by its
|
||||
// parent, so it does not need its own `.ng_deps.dart` file.
|
||||
if (directivesVisitor.isPart) return null;
|
||||
|
||||
var writer = new AsyncStringWriter();
|
||||
directivesVisitor.writeTo(writer, assetId);
|
||||
|
||||
writer
|
||||
..println('var _visited = false;')
|
||||
..println('void ${SETUP_METHOD_NAME}() {')
|
||||
..println('if (_visited) return; _visited = true;');
|
||||
|
||||
var declarationsCode =
|
||||
await _getAllDeclarations(reader, assetId, code, directivesVisitor);
|
||||
var declarationsVisitor = new _NgDepsDeclarationsVisitor(
|
||||
assetId,
|
||||
writer,
|
||||
new XhrImpl(reader, assetId),
|
||||
annotationMatcher,
|
||||
_interfaceMatcher,
|
||||
ngMeta,
|
||||
inlineViews: inlineViews);
|
||||
parseCompilationUnit(declarationsCode, name: '${assetId.path} and parts')
|
||||
.declarations
|
||||
.accept(declarationsVisitor);
|
||||
if (declarationsVisitor.shouldCreateNgDeps) {
|
||||
writer.println(';');
|
||||
}
|
||||
writer.println('}');
|
||||
|
||||
if (!directivesVisitor.shouldCreateNgDeps &&
|
||||
!declarationsVisitor.shouldCreateNgDeps) return null;
|
||||
|
||||
return writer.asyncToString();
|
||||
}
|
||||
|
||||
InterfaceMatcher _interfaceMatcher = new InterfaceMatcher();
|
||||
|
||||
/// Processes `visitor.parts`, reading and appending their contents to the
|
||||
/// original `code`.
|
||||
/// Order of `part`s is preserved. That is, if in the main library we have
|
||||
/// ```
|
||||
/// library main;
|
||||
///
|
||||
/// part 'lib1.dart'
|
||||
/// part 'lib2.dart'
|
||||
/// ```
|
||||
/// The output will first have the entirety of the original file, followed by
|
||||
/// the contents of lib1.dart followed by the contents of lib2.dart.
|
||||
Future<String> _getAllDeclarations(AssetReader reader, AssetId assetId,
|
||||
String code, _NgDepsDirectivesVisitor visitor) {
|
||||
if (visitor.parts.isEmpty) return new Future<String>.value(code);
|
||||
|
||||
var partsStart = visitor.parts.first.offset,
|
||||
partsEnd = visitor.parts.last.end;
|
||||
|
||||
var asyncWriter = new AsyncStringWriter(code.substring(0, partsStart));
|
||||
visitor.parts.forEach((partDirective) {
|
||||
var uri = stringLiteralToString(partDirective.uri);
|
||||
var partAssetId = uriToAssetId(assetId, uri, logger, null /* span */,
|
||||
errorOnAbsolute: false);
|
||||
asyncWriter.asyncPrint(reader.readAsString(partAssetId).then((partCode) {
|
||||
if (partCode == null || partCode.isEmpty) {
|
||||
logger.warning('Empty part at "${partDirective.uri}. Ignoring.',
|
||||
asset: partAssetId);
|
||||
return '';
|
||||
}
|
||||
// Remove any directives -- we just want declarations.
|
||||
var parsedDirectives = parseDirectives(partCode, name: uri).directives;
|
||||
return partCode.substring(parsedDirectives.last.end);
|
||||
}).catchError((err, stackTrace) {
|
||||
logger.warning(
|
||||
'Failed while reading part at ${partDirective.uri}. Ignoring.\n'
|
||||
'Error: $err\n'
|
||||
'Stack Trace: $stackTrace',
|
||||
asset: partAssetId,
|
||||
span: new SourceFile(code, url: path.basename(assetId.path))
|
||||
.span(partDirective.offset, partDirective.end));
|
||||
}));
|
||||
});
|
||||
asyncWriter.print(code.substring(partsEnd));
|
||||
|
||||
return asyncWriter.asyncToString();
|
||||
}
|
||||
|
||||
/// Visitor responsible for flattening directives passed to it.
|
||||
/// Once this has visited an Ast, use [#writeTo] to write out the directives
|
||||
/// for the .ng_deps.dart file. See [#writeTo] for details.
|
||||
class _NgDepsDirectivesVisitor extends Object with SimpleAstVisitor<Object> {
|
||||
/// Whether this library `imports` or `exports` any non-'dart:' libraries.
|
||||
bool _usesNonLangLibs = false;
|
||||
|
||||
/// Whether the file we are processing is a part, that is, whether we have
|
||||
/// visited a `part of` directive.
|
||||
bool _isPart = false;
|
||||
|
||||
// TODO(kegluneq): Support an intermediate representation of NgDeps and use it
|
||||
// instead of storing generated code.
|
||||
LibraryDirective _library = null;
|
||||
ScriptTag _scriptTag = null;
|
||||
final List<NamespaceDirective> _importAndExports = <NamespaceDirective>[];
|
||||
final List<PartDirective> _parts = <PartDirective>[];
|
||||
|
||||
bool get shouldCreateNgDeps {
|
||||
// If this library does not define an `@Injectable` and it does not import
|
||||
// any libaries that could, then we do not need to generate a `.ng_deps
|
||||
// .dart` file for it.
|
||||
if (!_usesNonLangLibs) return false;
|
||||
if (_isPart) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get usesNonLangLibs => _usesNonLangLibs;
|
||||
bool get isPart => _isPart;
|
||||
|
||||
/// In the order encountered in the source.
|
||||
Iterable<PartDirective> get parts => _parts;
|
||||
|
||||
@override
|
||||
Object visitScriptTag(ScriptTag node) {
|
||||
_scriptTag = node;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitCompilationUnit(CompilationUnit node) {
|
||||
node.directives.accept(this);
|
||||
return null;
|
||||
}
|
||||
|
||||
void _updateUsesNonLangLibs(UriBasedDirective directive) {
|
||||
_usesNonLangLibs = _usesNonLangLibs ||
|
||||
!stringLiteralToString(directive.uri).startsWith('dart:');
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitImportDirective(ImportDirective node) {
|
||||
_updateUsesNonLangLibs(node);
|
||||
_importAndExports.add(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitExportDirective(ExportDirective node) {
|
||||
_updateUsesNonLangLibs(node);
|
||||
_importAndExports.add(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitLibraryDirective(LibraryDirective node) {
|
||||
if (node != null) {
|
||||
_library = node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitPartDirective(PartDirective node) {
|
||||
_parts.add(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitPartOfDirective(PartOfDirective node) {
|
||||
_isPart = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Write the directives for the .ng_deps.dart for `processedFile` to
|
||||
/// `writer`. The .ng_deps.dart file has the same directives as
|
||||
/// `processedFile` with some exceptions (mentioned below).
|
||||
void writeTo(PrintWriter writer, AssetId processedFile) {
|
||||
var copyVisitor = new ToSourceVisitor(writer);
|
||||
|
||||
if (_scriptTag != null) {
|
||||
_scriptTag.accept(copyVisitor);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
if (_library != null && _library.name != null) {
|
||||
writer.print('library ');
|
||||
_library.name.accept(copyVisitor);
|
||||
writer.println('$DEPS_EXTENSION;');
|
||||
}
|
||||
|
||||
// We do not output [PartDirective]s, which would not be valid now that we
|
||||
// have changed the library.
|
||||
|
||||
// We need to import & export the original file.
|
||||
var origDartFile = path.basename(processedFile.path);
|
||||
writer.println('''import '$origDartFile';''');
|
||||
writer.println('''export '$origDartFile';''');
|
||||
|
||||
// Used to register reflective information.
|
||||
writer.println("import '$_REFLECTOR_IMPORT' as $_REF_PREFIX;");
|
||||
|
||||
_importAndExports.forEach((node) {
|
||||
if (node.isSynthetic) return;
|
||||
|
||||
// Ignore deferred imports here so as to not load the deferred libraries
|
||||
// code in the current library causing much of the code to not be
|
||||
// deferred. Instead `DeferredRewriter` will rewrite the code as to load
|
||||
// `ng_deps` in a deferred way.
|
||||
if (node is ImportDirective && node.deferredKeyword != null) return;
|
||||
|
||||
node.accept(copyVisitor);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor responsible for visiting a file's [Declaration]s and outputting the
|
||||
/// code necessary to register the file with the Angular 2 system.
|
||||
class _NgDepsDeclarationsVisitor extends Object with SimpleAstVisitor<Object> {
|
||||
final AsyncStringWriter writer;
|
||||
|
||||
/// The file we are processing.
|
||||
final AssetId assetId;
|
||||
|
||||
/// Output ngMeta information about aliases.
|
||||
// TODO(sigmund): add more to ngMeta. Currently this only contains aliasing
|
||||
// information, but we could produce here all the metadata we need and avoid
|
||||
// parsing the ngdeps files later.
|
||||
final NgMeta ngMeta;
|
||||
|
||||
/// Whether an Angular 2 `Injectable` has been found.
|
||||
bool _foundNgInjectable = false;
|
||||
|
||||
/// Visitor that writes out code for AstNodes visited.
|
||||
final ToSourceVisitor _copyVisitor;
|
||||
final FactoryTransformVisitor _factoryVisitor;
|
||||
final ParameterTransformVisitor _paramsVisitor;
|
||||
final AnnotationsTransformVisitor _metaVisitor;
|
||||
|
||||
/// Responsible for testing whether [Annotation]s are those recognized by
|
||||
/// Angular 2, for example `@Component`.
|
||||
final AnnotationMatcher _annotationMatcher;
|
||||
|
||||
/// Responsible for testing whether interfaces are recognized by Angular2,
|
||||
/// for example `OnChange`.
|
||||
final InterfaceMatcher _interfaceMatcher;
|
||||
|
||||
/// Used to fetch linked files.
|
||||
final XHR _xhr;
|
||||
|
||||
_NgDepsDeclarationsVisitor(
|
||||
AssetId assetId,
|
||||
AsyncStringWriter writer,
|
||||
XHR xhr,
|
||||
AnnotationMatcher annotationMatcher,
|
||||
InterfaceMatcher interfaceMatcher,
|
||||
this.ngMeta,
|
||||
{bool inlineViews})
|
||||
: writer = writer,
|
||||
_copyVisitor = new ToSourceVisitor(writer),
|
||||
_factoryVisitor = new FactoryTransformVisitor(writer),
|
||||
_paramsVisitor = new ParameterTransformVisitor(writer),
|
||||
_metaVisitor = new AnnotationsTransformVisitor(
|
||||
writer, xhr, annotationMatcher, interfaceMatcher, assetId,
|
||||
inlineViews: inlineViews),
|
||||
_annotationMatcher = annotationMatcher,
|
||||
_interfaceMatcher = interfaceMatcher,
|
||||
this.assetId = assetId,
|
||||
_xhr = xhr;
|
||||
|
||||
bool get shouldCreateNgDeps => _foundNgInjectable;
|
||||
|
||||
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;
|
||||
ctorName = ctorName == null
|
||||
? 'the unnamed constructor'
|
||||
: 'constructor "${ctorName}"';
|
||||
logger.warning('Found ${numCtorsFound} ctors for class ${node.name},'
|
||||
'Using ${ctorName}.');
|
||||
}
|
||||
return ctor;
|
||||
}
|
||||
|
||||
void _generateEmptyFactory(String typeName) {
|
||||
writer.print('() => new ${typeName}()');
|
||||
}
|
||||
|
||||
void _generateEmptyParams() => writer.print('const []');
|
||||
|
||||
@override
|
||||
Object visitClassDeclaration(ClassDeclaration node) {
|
||||
if (!node.metadata
|
||||
.any((a) => _annotationMatcher.hasMatch(a.name, assetId))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ctor = _getCtor(node);
|
||||
|
||||
_maybeWriteReflector();
|
||||
writer.print('..registerType(');
|
||||
node.name.accept(this);
|
||||
writer.print(', new ${_REF_PREFIX}.ReflectionInfo(');
|
||||
node.accept(_metaVisitor);
|
||||
writer.print(', ');
|
||||
if (ctor == null) {
|
||||
_generateEmptyParams();
|
||||
} else {
|
||||
ctor.accept(_paramsVisitor);
|
||||
}
|
||||
writer.print(', ');
|
||||
if (ctor == null) {
|
||||
_generateEmptyFactory(node.name.toString());
|
||||
} else {
|
||||
ctor.accept(_factoryVisitor);
|
||||
}
|
||||
if (node.implementsClause != null &&
|
||||
node.implementsClause.interfaces != null &&
|
||||
node.implementsClause.interfaces.isNotEmpty) {
|
||||
writer
|
||||
..print(', const [')
|
||||
..print(node.implementsClause.interfaces
|
||||
.map((interface) => interface.name)
|
||||
.join(', '))
|
||||
..print(']');
|
||||
}
|
||||
writer.print('))');
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
||||
// We process any top-level declaration that fits the directive-alias
|
||||
// declaration pattern. Ideally we would use an annotation on the field to
|
||||
// help us filter out only what's needed, but unfortunately TypeScript
|
||||
// doesn't support decorators on variable declarations (see
|
||||
// angular/angular#1747 and angular/ts2dart#249 for context).
|
||||
outer: for (var variable in node.variables.variables) {
|
||||
var initializer = variable.initializer;
|
||||
if (initializer != null && initializer is ListLiteral) {
|
||||
var otherNames = [];
|
||||
for (var exp in initializer.elements) {
|
||||
// Only simple identifiers are supported for now.
|
||||
// TODO(sigmund): add support for prefixes (see issue #3232).
|
||||
if (exp is! SimpleIdentifier) continue outer;
|
||||
otherNames.add(exp.name);
|
||||
}
|
||||
ngMeta.aliases[variable.name.name] = otherNames;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Object _nodeToSource(AstNode node) {
|
||||
if (node == null) return null;
|
||||
return node.accept(_copyVisitor);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitPrefixedIdentifier(PrefixedIdentifier node) =>
|
||||
_nodeToSource(node);
|
||||
|
||||
@override
|
||||
Object visitSimpleIdentifier(SimpleIdentifier node) => _nodeToSource(node);
|
||||
|
||||
@override
|
||||
bool visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if (!node.metadata
|
||||
.any((a) => _annotationMatcher.hasMatch(a.name, assetId))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
_maybeWriteReflector();
|
||||
writer.print('..registerFunction(');
|
||||
node.name.accept(this);
|
||||
writer.print(', new ${_REF_PREFIX}.ReflectionInfo(const [');
|
||||
node.metadata.accept(_metaVisitor);
|
||||
writer.print('], const [');
|
||||
node.functionExpression.parameters.accept(_paramsVisitor);
|
||||
writer.print(']))');
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Writes out the reflector variable the first time it is called.
|
||||
void _maybeWriteReflector() {
|
||||
if (_foundNgInjectable) return;
|
||||
_foundNgInjectable = true;
|
||||
|
||||
// The receiver for cascaded calls.
|
||||
writer.print('$_REF_PREFIX.$REFLECTOR_VAR_NAME');
|
||||
}
|
||||
}
|
||||
|
||||
const _REF_PREFIX = '_ngRef';
|
||||
const _REFLECTOR_IMPORT = 'package:angular2/src/reflection/reflection.dart';
|
@ -0,0 +1,58 @@
|
||||
library angular2.transform.directive_processor.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/options.dart';
|
||||
import 'package:angular2/src/transform/common/ng_meta.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'rewriter.dart';
|
||||
|
||||
/// Transformer responsible for processing all .dart assets and creating
|
||||
/// .ng_deps.dart files which register @Injectable annotated classes with the
|
||||
/// reflector.
|
||||
///
|
||||
/// This will also create .ng_deps.dart files for classes annotated
|
||||
/// with @Component, @View, @Directive, etc.
|
||||
///
|
||||
/// This transformer is the first phase in a two-phase transform. It should
|
||||
/// be followed by {@link DirectiveLinker}.
|
||||
class DirectiveProcessor extends Transformer {
|
||||
final TransformerOptions options;
|
||||
|
||||
DirectiveProcessor(this.options);
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.extension.endsWith('dart');
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
var asset = transform.primaryInput;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var ngMeta = new NgMeta.empty();
|
||||
var ngDepsSrc = await createNgDeps(
|
||||
reader, asset.id, options.annotationMatcher, ngMeta,
|
||||
inlineViews: options.inlineViews);
|
||||
if (ngDepsSrc != null && ngDepsSrc.isNotEmpty) {
|
||||
var ngDepsAssetId =
|
||||
transform.primaryInput.id.changeExtension(DEPS_EXTENSION);
|
||||
if (await transform.hasInput(ngDepsAssetId)) {
|
||||
log.logger.error('Clobbering ${ngDepsAssetId}. '
|
||||
'This probably will not end well');
|
||||
}
|
||||
transform.addOutput(new Asset.fromString(ngDepsAssetId, ngDepsSrc));
|
||||
}
|
||||
if (!ngMeta.isEmpty) {
|
||||
var ngAliasesId =
|
||||
transform.primaryInput.id.changeExtension(ALIAS_EXTENSION);
|
||||
transform.addOutput(new Asset.fromString(ngAliasesId,
|
||||
new JsonEncoder.withIndent(" ").convert(ngMeta.toJson())));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,444 @@
|
||||
library angular2.transform.directive_processor.visitors;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:analyzer/src/generated/java_core.dart';
|
||||
import 'package:angular2/metadata.dart' show LifecycleEvent;
|
||||
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 {
|
||||
bool _withParameterAnnotations = true;
|
||||
bool _withParameterTypes = true;
|
||||
bool _withParameterNames = true;
|
||||
final PrintWriter writer;
|
||||
|
||||
/// Maps field names to their declared types. This is populated whenever
|
||||
/// the listener visits a {@link ConstructorDeclaration} node.
|
||||
final Map<String, TypeName> _fieldNameToType = {};
|
||||
|
||||
_CtorTransformVisitor(PrintWriter writer)
|
||||
: this.writer = writer,
|
||||
super(writer);
|
||||
|
||||
void _visitNodeWithPrefix(String prefix, AstNode node) {
|
||||
if (node != null) {
|
||||
writer.print(prefix);
|
||||
node.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void _visitNodeWithSuffix(AstNode node, String suffix) {
|
||||
if (node != null) {
|
||||
node.accept(this);
|
||||
writer.print(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
void _visitNode(AstNode node) {
|
||||
if (node != null) {
|
||||
node.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// If `_withParameterTypes` is true, this method outputs `node`'s type. If
|
||||
/// `_withParameterNames` is true, this method outputs `node`'s identifier.
|
||||
Object _visitNormalFormalParameter(
|
||||
NodeList<Annotation> metadata, TypeName type, SimpleIdentifier name) {
|
||||
var needCompileTimeConstants = !_withParameterNames;
|
||||
var needType = _withParameterTypes && type != null;
|
||||
if (needType) {
|
||||
_visitNodeWithSuffix(type.name, ' ');
|
||||
if (!needCompileTimeConstants) {
|
||||
// Types with arguments are not compile-time constants.
|
||||
_visitNodeWithSuffix(type.typeArguments, ' ');
|
||||
}
|
||||
}
|
||||
if (_withParameterNames) {
|
||||
_visitNode(name);
|
||||
}
|
||||
if (_withParameterAnnotations && metadata != null) {
|
||||
assert(_withParameterTypes);
|
||||
for (var i = 0, iLen = metadata.length; i < iLen; ++i) {
|
||||
if (i != 0 || needType) {
|
||||
writer.print(', ');
|
||||
}
|
||||
metadata[i].accept(this);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _buildFieldMap(ConstructorDeclaration node) {
|
||||
ClassDeclaration clazz =
|
||||
node.getAncestor((node) => node is ClassDeclaration);
|
||||
_fieldNameToType.clear();
|
||||
clazz.members
|
||||
.where((member) => member is FieldDeclaration)
|
||||
.forEach((FieldDeclaration field) {
|
||||
var type = field.fields.type;
|
||||
if (type != null) {
|
||||
field.fields.variables.forEach((VariableDeclaration decl) {
|
||||
_fieldNameToType[decl.name.toString()] = type;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitSimpleFormalParameter(SimpleFormalParameter node) {
|
||||
return _visitNormalFormalParameter(
|
||||
node.metadata, node.type, node.identifier);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitFieldFormalParameter(FieldFormalParameter node) {
|
||||
if (node.parameters != null) {
|
||||
logger.error('Parameters in ctor not supported '
|
||||
'(${node.toSource()})');
|
||||
}
|
||||
var type = node.type;
|
||||
if (type == null) {
|
||||
type = _fieldNameToType[node.identifier.toString()];
|
||||
}
|
||||
return _visitNormalFormalParameter(node.metadata, type, node.identifier);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
|
||||
logger.error('Function typed formal parameters not supported '
|
||||
'(${node.toSource()})');
|
||||
return _visitNormalFormalParameter(node.metadata, null, node.identifier);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitDefaultFormalParameter(DefaultFormalParameter node) {
|
||||
_visitNode(node.parameter);
|
||||
// Ignore the declared default value.
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
/// Overridden to avoid outputting grouping operators for default parameters.
|
||||
Object visitFormalParameterList(FormalParameterList node) {
|
||||
writer.print('(');
|
||||
NodeList<FormalParameter> parameters = node.parameters;
|
||||
int size = parameters.length;
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i > 0) {
|
||||
writer.print(', ');
|
||||
}
|
||||
parameters[i].accept(this);
|
||||
}
|
||||
writer.print(')');
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitAnnotation(Annotation node) {
|
||||
var prefix =
|
||||
node.arguments != null && node.arguments.length > 0 ? 'const ' : '';
|
||||
_visitNodeWithPrefix(prefix, node.name);
|
||||
_visitNodeWithPrefix(".", node.constructorName);
|
||||
_visitNode(node.arguments);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// ToSourceVisitor designed to print 'parameters' values for Angular2's
|
||||
/// `registerType` calls.
|
||||
class ParameterTransformVisitor extends _CtorTransformVisitor {
|
||||
ParameterTransformVisitor(PrintWriter writer) : super(writer) {
|
||||
_withParameterNames = false;
|
||||
_withParameterTypes = true;
|
||||
_withParameterAnnotations = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||
_buildFieldMap(node);
|
||||
writer.print('const [');
|
||||
_visitNode(node.parameters);
|
||||
writer.print(']');
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitFormalParameterList(FormalParameterList node) {
|
||||
NodeList<FormalParameter> parameters = node.parameters;
|
||||
for (int i = 0, iLen = parameters.length; i < iLen; i++) {
|
||||
if (i > 0) {
|
||||
writer.print(', ');
|
||||
}
|
||||
// TODO(kegluneq): Include annotations on parameters.
|
||||
writer.print('const [');
|
||||
parameters[i].accept(this);
|
||||
writer.print(']');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// ToSourceVisitor designed to print 'factory' values for Angular2's
|
||||
/// `registerType` calls.
|
||||
class FactoryTransformVisitor extends _CtorTransformVisitor {
|
||||
FactoryTransformVisitor(PrintWriter writer) : super(writer) {
|
||||
_withParameterAnnotations = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||
_buildFieldMap(node);
|
||||
_withParameterNames = true;
|
||||
_withParameterTypes = true;
|
||||
_visitNode(node.parameters);
|
||||
writer.print(' => new ');
|
||||
_visitNode(node.returnType);
|
||||
_visitNodeWithPrefix(".", node.name);
|
||||
|
||||
_withParameterTypes = false;
|
||||
_visitNode(node.parameters);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
final Set<String> _ifaceLifecycleEntries = new Set<String>();
|
||||
bool _isLifecycleWritten = false;
|
||||
bool _isProcessingView = false;
|
||||
bool _isProcessingDirective = false;
|
||||
String _ifaceLifecyclePrefix = '';
|
||||
|
||||
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 populateImport = (Identifier name) {
|
||||
if (_ifaceLifecyclePrefix.isNotEmpty) return;
|
||||
var import = _interfaceMatcher.getMatchingImport(name, _assetId);
|
||||
_ifaceLifecyclePrefix =
|
||||
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)) {
|
||||
_ifaceLifecycleEntries.add('${LifecycleEvent.onChange}');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnDestroy(name, _assetId)) {
|
||||
_ifaceLifecycleEntries.add('${LifecycleEvent.onDestroy}');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnCheck(name, _assetId)) {
|
||||
_ifaceLifecycleEntries.add('${LifecycleEvent.onCheck}');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnInit(name, _assetId)) {
|
||||
_ifaceLifecycleEntries.add('${LifecycleEvent.onInit}');
|
||||
populateImport(name);
|
||||
}
|
||||
if (_interfaceMatcher.isOnAllChangesDone(name, _assetId)) {
|
||||
_ifaceLifecycleEntries.add('${LifecycleEvent.onAllChangesDone}');
|
||||
populateImport(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetState() {
|
||||
_isLifecycleWritten = _isProcessingView = _isProcessingDirective = false;
|
||||
_ifaceLifecycleEntries.clear();
|
||||
_ifaceLifecyclePrefix = '';
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitClassDeclaration(ClassDeclaration node) {
|
||||
_populateLifecycleValue(node);
|
||||
|
||||
writer.print('const [');
|
||||
var size = node.metadata.length;
|
||||
for (var i = 0; i < size; ++i) {
|
||||
if (i > 0) {
|
||||
writer.print(', ');
|
||||
}
|
||||
node.metadata[i].accept(this);
|
||||
}
|
||||
writer.print(']');
|
||||
|
||||
_resetState();
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitAnnotation(Annotation node) {
|
||||
writer.print('const ');
|
||||
if (node.name != null) {
|
||||
_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.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 (!_isLifecycleWritten && _isProcessingDirective) {
|
||||
var lifecycleValue = _getLifecycleValue();
|
||||
if (lifecycleValue.isNotEmpty) {
|
||||
writer.print(', lifecycle: $lifecycleValue');
|
||||
_isLifecycleWritten = true;
|
||||
}
|
||||
}
|
||||
writer.print(')');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _getLifecycleValue() {
|
||||
if (_ifaceLifecycleEntries.isNotEmpty) {
|
||||
var entries = _ifaceLifecycleEntries.toList();
|
||||
entries.sort();
|
||||
return 'const [${_ifaceLifecyclePrefix}'
|
||||
'${entries.join(", ${_ifaceLifecyclePrefix}")}]';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/// These correspond to the annotation parameters.
|
||||
@override
|
||||
Object visitNamedExpression(NamedExpression node) {
|
||||
if (!_isProcessingView && !_isProcessingDirective) {
|
||||
return super.visitNamedExpression(node);
|
||||
}
|
||||
// TODO(kegluneq): Remove this limitation.
|
||||
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
|
||||
return super.visitNamedExpression(node);
|
||||
}
|
||||
var keyString = '${node.name.label}';
|
||||
if (_isProcessingView && _inlineViews) {
|
||||
var isSuccess = this._inlineView(keyString, node.expression);
|
||||
if (isSuccess) return null;
|
||||
}
|
||||
if (_isProcessingDirective && keyString == 'lifecycle') {
|
||||
var isSuccess = _populateLifecycleFromNamedExpression(node.expression);
|
||||
if (isSuccess) {
|
||||
_isLifecycleWritten = true;
|
||||
writer.print('lifecycle: ${_getLifecycleValue()}');
|
||||
return null;
|
||||
} else {
|
||||
logger.warning('Failed to parse `lifecycle` value. '
|
||||
'The following `LifecycleEvent`s may not be called: '
|
||||
'(${_ifaceLifecycleEntries.join(', ')})');
|
||||
_isLifecycleWritten = true;
|
||||
// Do not return -- we will use the default processing here, maintaining
|
||||
// the original value for `lifecycle`.
|
||||
}
|
||||
}
|
||||
return super.visitNamedExpression(node);
|
||||
}
|
||||
|
||||
/// Populates the lifecycle values from explicitly declared values.
|
||||
/// Returns whether `node` was successfully processed.
|
||||
bool _populateLifecycleFromNamedExpression(AstNode node) {
|
||||
var nodeVal = node.toSource();
|
||||
for (var evt in LifecycleEvent.values) {
|
||||
var evtStr = '$evt';
|
||||
if (nodeVal.contains(evtStr)) {
|
||||
_ifaceLifecycleEntries.add(evtStr);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Inlines the template and/or style refered to by `keyString`.
|
||||
/// Returns whether the `keyString` value was successfully processed.
|
||||
bool _inlineView(String keyString, AstNode node) {
|
||||
if (keyString == 'templateUrl') {
|
||||
// Inline the templateUrl
|
||||
var url = node.accept(_evaluator);
|
||||
if (url is String) {
|
||||
writer.print("template: r'''");
|
||||
writer.asyncPrint(_readOrEmptyString(url));
|
||||
writer.print("'''");
|
||||
|
||||
// We keep the templateUrl in case the body of the template includes
|
||||
// relative urls that might be inlined later on (e.g. @import
|
||||
// directives or url() css values in style tags).
|
||||
writer.print(", templateUrl: r'$url'");
|
||||
return true;
|
||||
} else {
|
||||
logger.warning('template url is not a String $url');
|
||||
}
|
||||
} else if (keyString == 'styleUrls') {
|
||||
// Inline the styleUrls
|
||||
var urls = node.accept(_evaluator);
|
||||
writer.print('styles: const [');
|
||||
for (var url in urls) {
|
||||
if (url is String) {
|
||||
writer.print("r'''");
|
||||
writer.asyncPrint(_readOrEmptyString(url));
|
||||
writer.print("''', ");
|
||||
} else {
|
||||
logger.warning('style url is not a String ${url}');
|
||||
}
|
||||
}
|
||||
writer.print(']');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Attempts to read the content from {@link url}, if it returns null then
|
||||
/// just return the empty string.
|
||||
Future<String> _readOrEmptyString(String url) async {
|
||||
var content = await _xhr.get(url);
|
||||
if (content == null) {
|
||||
content = '';
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
library angular2.transform.reflection_remover.ast_tester;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
|
||||
/// An object that checks for {@link ReflectionCapabilities} syntactically, that is,
|
||||
/// without resolution information.
|
||||
class AstTester {
|
||||
const AstTester();
|
||||
|
||||
bool isNewReflectionCapabilities(InstanceCreationExpression node) =>
|
||||
'${node.constructorName.type.name}' == REFLECTION_CAPABILITIES_NAME;
|
||||
|
||||
bool isReflectionCapabilitiesImport(ImportDirective node) {
|
||||
return node.uri.stringValue ==
|
||||
"package:angular2/src/reflection/reflection_capabilities.dart";
|
||||
}
|
||||
|
||||
bool isBootstrapImport(ImportDirective node) {
|
||||
return node.uri.stringValue == "package:angular2/bootstrap.dart";
|
||||
}
|
||||
}
|
||||
|
||||
/// An object that checks for {@link ReflectionCapabilities} using a fully resolved
|
||||
/// Ast.
|
||||
class ResolvedTester implements AstTester {
|
||||
final ClassElement _forbiddenClass;
|
||||
|
||||
ResolvedTester(this._forbiddenClass);
|
||||
|
||||
bool isNewReflectionCapabilities(InstanceCreationExpression node) {
|
||||
var typeElement = node.constructorName.type.name.bestElement;
|
||||
return typeElement != null && typeElement == _forbiddenClass;
|
||||
}
|
||||
|
||||
bool isReflectionCapabilitiesImport(ImportDirective node) {
|
||||
return node.uriElement == _forbiddenClass.library;
|
||||
}
|
||||
|
||||
bool isBootstrapImport(ImportDirective node) {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
library angular2.transform.reflection_remover.codegen;
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
|
||||
class Codegen {
|
||||
static const _PREFIX_BASE = 'ngStaticInit';
|
||||
|
||||
/// The prefix used to import our generated file.
|
||||
final String prefix;
|
||||
|
||||
/// The import uris
|
||||
final Iterable<String> importUris;
|
||||
|
||||
Codegen(String reflectionEntryPointPath, Iterable<String> newEntryPointPaths,
|
||||
{String prefix})
|
||||
: this.prefix = prefix == null ? _PREFIX_BASE : prefix,
|
||||
importUris = newEntryPointPaths.map((p) => path
|
||||
.relative(p, from: path.dirname(reflectionEntryPointPath))
|
||||
.replaceAll('\\', '/')) {
|
||||
if (this.prefix.isEmpty) throw new ArgumentError.value('(empty)', 'prefix');
|
||||
}
|
||||
|
||||
/// Generates code to import the library containing the method which sets up
|
||||
/// Angular2 reflection statically.
|
||||
///
|
||||
/// The code generated here should follow the example of code generated for
|
||||
/// an {@link ImportDirective} node.
|
||||
String codegenImport() {
|
||||
var count = 0;
|
||||
return importUris
|
||||
.map((importUri) => 'import \'${importUri}\' as ${prefix}${count++};')
|
||||
.join('');
|
||||
}
|
||||
|
||||
/// Generates code to call the method which sets up Angular2 reflection
|
||||
/// statically.
|
||||
String codegenSetupReflectionCall() {
|
||||
var count = 0;
|
||||
return importUris
|
||||
.map((_) => '${prefix}${count++}.${SETUP_METHOD_NAME}();')
|
||||
.join('');
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
library angular2.transform.reflection_remover.remove_reflection_capabilities;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/mirror_mode.dart';
|
||||
|
||||
import 'codegen.dart';
|
||||
import 'rewriter.dart';
|
||||
|
||||
/// Finds the call to the Angular2 `ReflectionCapabilities` constructor
|
||||
/// in `reflectionEntryPoint` and replaces it with a call to
|
||||
/// `setupReflection` in `newEntryPoint`.
|
||||
///
|
||||
/// This only searches the code in `reflectionEntryPoint`, not `part`s,
|
||||
/// `import`s, `export`s, etc.
|
||||
Future<String> removeReflectionCapabilities(AssetReader reader,
|
||||
AssetId reflectionEntryPoint, Iterable<AssetId> newEntryPoints,
|
||||
{MirrorMode mirrorMode: MirrorMode.none,
|
||||
bool writeStaticInit: true}) async {
|
||||
var code = await reader.readAsString(reflectionEntryPoint);
|
||||
var reflectionEntryPointPath = reflectionEntryPoint.path;
|
||||
var newEntryPointPaths = newEntryPoints.map((id) => id.path);
|
||||
|
||||
var codegen = new Codegen(reflectionEntryPointPath, newEntryPointPaths);
|
||||
return new Rewriter(code, codegen,
|
||||
mirrorMode: mirrorMode, writeStaticInit: writeStaticInit)
|
||||
.rewrite(parseCompilationUnit(code, name: reflectionEntryPointPath));
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
library angular2.transform.reflection_remover.rewriter;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/mirror_mode.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'ast_tester.dart';
|
||||
import 'codegen.dart';
|
||||
|
||||
class Rewriter {
|
||||
final String _code;
|
||||
final Codegen _codegen;
|
||||
final AstTester _tester;
|
||||
final MirrorMode _mirrorMode;
|
||||
final bool _writeStaticInit;
|
||||
|
||||
Rewriter(this._code, this._codegen,
|
||||
{AstTester tester,
|
||||
MirrorMode mirrorMode: MirrorMode.none,
|
||||
bool writeStaticInit: true})
|
||||
: _mirrorMode = mirrorMode,
|
||||
_writeStaticInit = writeStaticInit,
|
||||
_tester = tester == null ? const AstTester() : tester;
|
||||
|
||||
/// Rewrites the provided code removing imports of the
|
||||
/// {@link ReflectionCapabilities} library and instantiations of
|
||||
/// {@link ReflectionCapabilities}, as detected by the (potentially) provided
|
||||
/// {@link AstTester}.
|
||||
///
|
||||
/// To the extent possible, this method does not change line numbers or
|
||||
/// offsets in the provided code to facilitate debugging via source maps.
|
||||
String rewrite(CompilationUnit node) {
|
||||
if (node == null) throw new ArgumentError.notNull('node');
|
||||
|
||||
var visitor = new _RewriterVisitor(this);
|
||||
|
||||
node.accept(visitor);
|
||||
|
||||
return visitor.outputRewrittenCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor responsible for rewriting the Angular 2 code which instantiates
|
||||
/// {@link ReflectionCapabilities} and removing its associated import.
|
||||
///
|
||||
/// This breaks our dependency on dart:mirrors, which enables smaller code
|
||||
/// size and better performance.
|
||||
class _RewriterVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||
final Rewriter _rewriter;
|
||||
final buf = new StringBuffer();
|
||||
final reflectionCapabilityAssignments = [];
|
||||
|
||||
int _currentIndex = 0;
|
||||
bool _setupAdded = false;
|
||||
bool _importAdded = false;
|
||||
|
||||
_RewriterVisitor(this._rewriter);
|
||||
|
||||
@override
|
||||
Object visitImportDirective(ImportDirective node) {
|
||||
buf.write(_rewriter._code.substring(_currentIndex, node.offset));
|
||||
_currentIndex = node.offset;
|
||||
if (_rewriter._tester.isReflectionCapabilitiesImport(node)) {
|
||||
_rewriteReflectionCapabilitiesImport(node);
|
||||
} else if (_rewriter._tester.isBootstrapImport(node)) {
|
||||
_rewriteBootstrapImportToStatic(node);
|
||||
}
|
||||
if (!_importAdded && _rewriter._writeStaticInit) {
|
||||
// Add imports for ng_deps (once)
|
||||
buf.write(_rewriter._codegen.codegenImport());
|
||||
_importAdded = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitAssignmentExpression(AssignmentExpression node) {
|
||||
if (node.rightHandSide is InstanceCreationExpression &&
|
||||
_rewriter._tester.isNewReflectionCapabilities(node.rightHandSide)) {
|
||||
reflectionCapabilityAssignments.add(node);
|
||||
_rewriteReflectionCapabilitiesAssignment(node);
|
||||
}
|
||||
return super.visitAssignmentExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
|
||||
if (_rewriter._tester.isNewReflectionCapabilities(node) &&
|
||||
!reflectionCapabilityAssignments.contains(node.parent)) {
|
||||
logger.error('Unexpected format in creation of '
|
||||
'${REFLECTION_CAPABILITIES_NAME}');
|
||||
}
|
||||
return super.visitInstanceCreationExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitMethodInvocation(MethodInvocation node) {
|
||||
if (node.methodName.toString() == BOOTSTRAP_NAME) {
|
||||
_rewriteBootstrapCallToStatic(node);
|
||||
}
|
||||
return super.visitMethodInvocation(node);
|
||||
}
|
||||
|
||||
String outputRewrittenCode() {
|
||||
if (_currentIndex < _rewriter._code.length) {
|
||||
buf.write(_rewriter._code.substring(_currentIndex));
|
||||
}
|
||||
return '$buf';
|
||||
}
|
||||
|
||||
_rewriteBootstrapImportToStatic(ImportDirective node) {
|
||||
if (_rewriter._writeStaticInit) {
|
||||
// rewrite `bootstrap.dart` to `bootstrap_static.dart`
|
||||
buf.write(_rewriter._code.substring(_currentIndex, node.offset));
|
||||
// TODO(yjbanov): handle import "..." show/hide ...
|
||||
buf.write("import 'package:angular2/bootstrap_static.dart';");
|
||||
} else {
|
||||
// leave it as is
|
||||
buf.write(_rewriter._code.substring(_currentIndex, node.end));
|
||||
}
|
||||
_currentIndex = node.end;
|
||||
}
|
||||
|
||||
_rewriteBootstrapCallToStatic(MethodInvocation node) {
|
||||
if (_rewriter._writeStaticInit) {
|
||||
buf.write(_rewriter._code.substring(_currentIndex, node.offset));
|
||||
|
||||
var args = node.argumentList.arguments;
|
||||
int numArgs = node.argumentList.arguments.length;
|
||||
if (numArgs < 1 || numArgs > 2) {
|
||||
logger.warning('`bootstrap` does not support $numArgs arguments. '
|
||||
'Found bootstrap${node.argumentList}. Transform may not succeed.');
|
||||
}
|
||||
|
||||
var reflectorInit =
|
||||
_setupAdded ? '' : ', () { ${_getStaticReflectorInitBlock()} }';
|
||||
|
||||
// rewrite `bootstrap(...)` to `bootstrapStatic(...)`
|
||||
buf.write('bootstrapStatic(${args[0]}');
|
||||
if (numArgs == 1) {
|
||||
// bootstrap args are positional, so before we pass reflectorInit code
|
||||
// we need to pass `null` for DI bindings.
|
||||
if (reflectorInit.isNotEmpty) {
|
||||
buf.write(', null');
|
||||
}
|
||||
} else {
|
||||
// pass DI bindings
|
||||
buf.write(', ${args[1]}');
|
||||
}
|
||||
buf.write(reflectorInit);
|
||||
buf.write(')');
|
||||
_setupAdded = true;
|
||||
} else {
|
||||
// leave it as is
|
||||
buf.write(_rewriter._code.substring(_currentIndex, node.end));
|
||||
}
|
||||
_currentIndex = node.end;
|
||||
}
|
||||
|
||||
String _getStaticReflectorInitBlock() {
|
||||
return _rewriter._codegen.codegenSetupReflectionCall();
|
||||
}
|
||||
|
||||
_rewriteReflectionCapabilitiesImport(ImportDirective node) {
|
||||
buf.write(_rewriter._code.substring(_currentIndex, node.offset));
|
||||
if ('${node.prefix}' == _rewriter._codegen.prefix) {
|
||||
logger.warning(
|
||||
'Found import prefix "${_rewriter._codegen.prefix}" in source file.'
|
||||
' Transform may not succeed.');
|
||||
}
|
||||
if (_rewriter._mirrorMode != MirrorMode.none) {
|
||||
buf.write(_importDebugReflectionCapabilities(node));
|
||||
} else {
|
||||
buf.write(_commentedNode(node));
|
||||
}
|
||||
_currentIndex = node.end;
|
||||
}
|
||||
|
||||
_rewriteReflectionCapabilitiesAssignment(AssignmentExpression assignNode) {
|
||||
var node = assignNode;
|
||||
while (node.parent is ExpressionStatement) {
|
||||
node = node.parent;
|
||||
}
|
||||
buf.write(_rewriter._code.substring(_currentIndex, node.offset));
|
||||
if (_rewriter._writeStaticInit && !_setupAdded) {
|
||||
buf.write(_getStaticReflectorInitBlock());
|
||||
_setupAdded = true;
|
||||
}
|
||||
switch (_rewriter._mirrorMode) {
|
||||
case MirrorMode.debug:
|
||||
buf.write(node);
|
||||
break;
|
||||
case MirrorMode.verbose:
|
||||
buf.write(_instantiateVerboseReflectionCapabilities(assignNode));
|
||||
break;
|
||||
case MirrorMode.none:
|
||||
default:
|
||||
buf.write(_commentedNode(node));
|
||||
break;
|
||||
}
|
||||
_currentIndex = node.end;
|
||||
}
|
||||
|
||||
String _commentedNode(AstNode node) {
|
||||
return '/*${_rewriter._code.substring(node.offset, node.end)}*/';
|
||||
}
|
||||
}
|
||||
|
||||
String _importDebugReflectionCapabilities(ImportDirective node) {
|
||||
var uri = '${node.uri}';
|
||||
uri = path
|
||||
.join(path.dirname(uri), 'debug_${path.basename(uri)}')
|
||||
.replaceAll('\\', '/');
|
||||
var asClause = node.prefix != null ? ' as ${node.prefix}' : '';
|
||||
return 'import $uri$asClause;';
|
||||
}
|
||||
|
||||
String _instantiateVerboseReflectionCapabilities(
|
||||
AssignmentExpression assignNode) {
|
||||
if (assignNode.rightHandSide is! InstanceCreationExpression) {
|
||||
return '$assignNode;';
|
||||
}
|
||||
var rhs = (assignNode.rightHandSide as InstanceCreationExpression);
|
||||
return '${assignNode.leftHandSide} ${assignNode.operator} '
|
||||
'new ${rhs.constructorName}(verbose: true);';
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
library angular2.transform.reflection_remover.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/mirror_mode.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/options.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'remove_reflection_capabilities.dart';
|
||||
|
||||
/// Transformer responsible for removing the import and instantiation of
|
||||
/// {@link ReflectionCapabilities}.
|
||||
///
|
||||
/// The goal of this is to break the app's dependency on dart:mirrors.
|
||||
///
|
||||
/// This transformer assumes that {@link DirectiveProcessor} and {@link DirectiveLinker}
|
||||
/// have already been run and that a .ng_deps.dart file has been generated for
|
||||
/// {@link options.entryPoint}. The instantiation of {@link ReflectionCapabilities} is
|
||||
/// replaced by calling `setupReflection` in that .ng_deps.dart file.
|
||||
class ReflectionRemover extends Transformer {
|
||||
final TransformerOptions options;
|
||||
|
||||
ReflectionRemover(this.options);
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => options.reflectionEntryPoints != null &&
|
||||
options.reflectionEntryPoints.contains(id.path);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
var newEntryPoints = options.entryPoints.map((entryPoint) {
|
||||
return new AssetId(transform.primaryInput.id.package, entryPoint)
|
||||
.changeExtension(DEPS_EXTENSION);
|
||||
});
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
|
||||
var mirrorMode = options.mirrorMode;
|
||||
var writeStaticInit = options.initReflector;
|
||||
if (options.modeName == TRANSFORM_DYNAMIC_MODE) {
|
||||
mirrorMode = MirrorMode.debug;
|
||||
writeStaticInit = false;
|
||||
log.logger.info('Running in "${options.modeName}", '
|
||||
'mirrorMode: ${mirrorMode}, '
|
||||
'writeStaticInit: ${writeStaticInit}.');
|
||||
}
|
||||
|
||||
var transformedCode = await removeReflectionCapabilities(
|
||||
reader, transform.primaryInput.id, newEntryPoints,
|
||||
mirrorMode: mirrorMode, writeStaticInit: writeStaticInit);
|
||||
transform.addOutput(
|
||||
new Asset.fromString(transform.primaryInput.id, transformedCode));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,497 @@
|
||||
library angular2.transform.template_compiler.change_detector_codegen;
|
||||
|
||||
import 'package:angular2/src/change_detection/change_detection_util.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_facade.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_logic_util.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_name_util.dart';
|
||||
import 'package:angular2/src/change_detection/directive_record.dart';
|
||||
import 'package:angular2/src/change_detection/interfaces.dart';
|
||||
import 'package:angular2/src/change_detection/proto_change_detector.dart';
|
||||
import 'package:angular2/src/change_detection/proto_record.dart';
|
||||
import 'package:angular2/src/change_detection/event_binding.dart';
|
||||
import 'package:angular2/src/change_detection/binding_record.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_facade.dart' show codify;
|
||||
import 'package:angular2/src/facade/lang.dart' show BaseException;
|
||||
|
||||
/// Responsible for generating change detector classes for Angular 2.
|
||||
///
|
||||
/// This code should be kept in sync with the `ChangeDetectorJITGenerator`
|
||||
/// class. If you make updates here, please make equivalent changes there.
|
||||
class Codegen {
|
||||
/// Stores the generated class definitions.
|
||||
final StringBuffer _buf = new StringBuffer();
|
||||
|
||||
/// Stores all generated initialization code.
|
||||
final StringBuffer _initBuf = new StringBuffer();
|
||||
|
||||
/// The names of already generated classes.
|
||||
final Set<String> _names = new Set<String>();
|
||||
|
||||
/// Generates a change detector class with name `changeDetectorTypeName`,
|
||||
/// which must not conflict with other generated classes in the same
|
||||
/// `.ng_deps.dart` file. The change detector is used to detect changes in
|
||||
/// Objects of type `typeName`.
|
||||
void generate(String typeName, String changeDetectorTypeName,
|
||||
ChangeDetectorDefinition def) {
|
||||
if (_names.contains(changeDetectorTypeName)) {
|
||||
throw new BaseException(
|
||||
'Change detector named "${changeDetectorTypeName}" for ${typeName} '
|
||||
'conflicts with an earlier generated change detector class.');
|
||||
}
|
||||
_names.add(changeDetectorTypeName);
|
||||
new _CodegenState(typeName, changeDetectorTypeName, def)
|
||||
.._writeToBuf(_buf)
|
||||
.._writeInitToBuf(_initBuf);
|
||||
}
|
||||
|
||||
/// Gets all imports necessary for the generated code.
|
||||
String get imports {
|
||||
return _buf.isEmpty
|
||||
? ''
|
||||
: '''import '$_PREGEN_PROTO_CHANGE_DETECTOR_IMPORT' as $_GEN_PREFIX;''';
|
||||
}
|
||||
|
||||
bool get isEmpty => _buf.isEmpty;
|
||||
|
||||
/// Gets the initilization code that registers the generated classes with
|
||||
/// the Angular 2 change detection system.
|
||||
String get initialize => '$_initBuf';
|
||||
|
||||
@override
|
||||
String toString() => '$_buf';
|
||||
}
|
||||
|
||||
/// The state needed to generate a change detector for a single `Component`.
|
||||
class _CodegenState {
|
||||
/// The `id` of the `ChangeDetectorDefinition` we are generating this class
|
||||
/// for.
|
||||
final String _changeDetectorDefId;
|
||||
|
||||
/// The name of the `Type` this change detector is generated for. For example,
|
||||
/// this is `MyComponent` if the generated class will detect changes in
|
||||
/// `MyComponent` objects.
|
||||
final String _contextTypeName;
|
||||
|
||||
/// The name of the generated change detector class. This is an implementation
|
||||
/// detail and should not be visible to users.
|
||||
final String _changeDetectorTypeName;
|
||||
final String _changeDetectionStrategy;
|
||||
final List<DirectiveRecord> _directiveRecords;
|
||||
final List<ProtoRecord> _records;
|
||||
final List<EventBinding> _eventBindings;
|
||||
final CodegenLogicUtil _logic;
|
||||
final CodegenNameUtil _names;
|
||||
final ChangeDetectorGenConfig _genConfig;
|
||||
final List<BindingTarget> _propertyBindingTargets;
|
||||
|
||||
_CodegenState._(
|
||||
this._changeDetectorDefId,
|
||||
this._contextTypeName,
|
||||
this._changeDetectorTypeName,
|
||||
this._changeDetectionStrategy,
|
||||
this._records,
|
||||
this._propertyBindingTargets,
|
||||
this._eventBindings,
|
||||
this._directiveRecords,
|
||||
this._logic,
|
||||
this._names,
|
||||
this._genConfig);
|
||||
|
||||
factory _CodegenState(String typeName, String changeDetectorTypeName,
|
||||
ChangeDetectorDefinition def) {
|
||||
var protoRecords = createPropertyRecords(def);
|
||||
var eventBindings = createEventRecords(def);
|
||||
var propertyBindingTargets = def.bindingRecords.map((b) => b.target).toList();
|
||||
|
||||
var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, _UTIL);
|
||||
var logic = new CodegenLogicUtil(names, _UTIL, def.strategy);
|
||||
return new _CodegenState._(
|
||||
def.id,
|
||||
typeName,
|
||||
changeDetectorTypeName,
|
||||
def.strategy,
|
||||
protoRecords,
|
||||
propertyBindingTargets,
|
||||
eventBindings,
|
||||
def.directiveRecords,
|
||||
logic,
|
||||
names,
|
||||
def.genConfig);
|
||||
}
|
||||
|
||||
void _writeToBuf(StringBuffer buf) {
|
||||
buf.write('''\n
|
||||
class $_changeDetectorTypeName extends $_BASE_CLASS<$_contextTypeName> {
|
||||
${_genDeclareFields()}
|
||||
|
||||
$_changeDetectorTypeName(dispatcher)
|
||||
: super(${codify(_changeDetectorDefId)},
|
||||
dispatcher, ${_records.length},
|
||||
${_changeDetectorTypeName}.gen_propertyBindingTargets,
|
||||
${_changeDetectorTypeName}.gen_directiveIndices,
|
||||
${codify(_changeDetectionStrategy)}) {
|
||||
dehydrateDirectives(false);
|
||||
}
|
||||
|
||||
void detectChangesInRecordsInternal(throwOnChange) {
|
||||
${_names.genInitLocals()}
|
||||
var $_IS_CHANGED_LOCAL = false;
|
||||
var $_CHANGES_LOCAL = null;
|
||||
|
||||
${_records.map(_genRecord).join('')}
|
||||
|
||||
${_names.getAlreadyCheckedName()} = true;
|
||||
}
|
||||
|
||||
${_maybeGenHandleEventInternal()}
|
||||
|
||||
${_genCheckNoChanges()}
|
||||
|
||||
${_maybeGenCallOnAllChangesDone()}
|
||||
|
||||
${_maybeGenHydrateDirectives()}
|
||||
|
||||
${_maybeGenDehydrateDirectives()}
|
||||
|
||||
${_genPropertyBindingTargets()};
|
||||
|
||||
${_genDirectiveIndices()};
|
||||
|
||||
static $_GEN_PREFIX.ProtoChangeDetector
|
||||
$PROTO_CHANGE_DETECTOR_FACTORY_METHOD(
|
||||
$_GEN_PREFIX.ChangeDetectorDefinition def) {
|
||||
return new $_GEN_PREFIX.PregenProtoChangeDetector(
|
||||
(a) => new $_changeDetectorTypeName(a),
|
||||
def);
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
String _genPropertyBindingTargets() {
|
||||
var targets = _logic.genPropertyBindingTargets(_propertyBindingTargets, this._genConfig.genDebugInfo);
|
||||
return "static var gen_propertyBindingTargets = ${targets}";
|
||||
}
|
||||
|
||||
String _genDirectiveIndices() {
|
||||
var indices = _logic.genDirectiveIndices(_directiveRecords);
|
||||
return "static var gen_directiveIndices = ${indices}";
|
||||
}
|
||||
|
||||
String _maybeGenHandleEventInternal() {
|
||||
if (_eventBindings.length > 0) {
|
||||
var handlers = _eventBindings.map((eb) => _genEventBinding(eb)).join("\n");
|
||||
return '''
|
||||
handleEventInternal(eventName, elIndex, locals) {
|
||||
var ${this._names.getPreventDefaultAccesor()} = false;
|
||||
${this._names.genInitEventLocals()}
|
||||
${handlers}
|
||||
return ${this._names.getPreventDefaultAccesor()};
|
||||
}
|
||||
''';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _genEventBinding(EventBinding eb) {
|
||||
var recs = eb.records.map((r) => _genEventBindingEval(eb, r)).join("\n");
|
||||
return '''
|
||||
if (eventName == "${eb.eventName}" && elIndex == ${eb.elIndex}) {
|
||||
${recs}
|
||||
}''';
|
||||
}
|
||||
|
||||
String _genEventBindingEval(EventBinding eb, ProtoRecord r){
|
||||
if (r.lastInBinding) {
|
||||
var evalRecord = _logic.genEventBindingEvalValue(eb, r);
|
||||
var markPath = _genMarkPathToRootAsCheckOnce(r);
|
||||
var prevDefault = _genUpdatePreventDefault(eb, r);
|
||||
return "${evalRecord}\n${markPath}\n${prevDefault}";
|
||||
} else {
|
||||
return _logic.genEventBindingEvalValue(eb, r);
|
||||
}
|
||||
}
|
||||
|
||||
String _genMarkPathToRootAsCheckOnce(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
if (!br.isDefaultChangeDetection()) {
|
||||
return "${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String _genUpdatePreventDefault(EventBinding eb, ProtoRecord r) {
|
||||
var local = this._names.getEventLocalName(eb, r.selfIndex);
|
||||
return """if (${local} == false) { ${_names.getPreventDefaultAccesor()} = true; }""";
|
||||
}
|
||||
|
||||
void _writeInitToBuf(StringBuffer buf) {
|
||||
buf.write('''
|
||||
$_GEN_PREFIX.preGeneratedProtoDetectors['$_changeDetectorDefId'] =
|
||||
$_changeDetectorTypeName.newProtoChangeDetector;
|
||||
''');
|
||||
}
|
||||
|
||||
String _maybeGenDehydrateDirectives() {
|
||||
var destroyPipesParamName = 'destroyPipes';
|
||||
var destroyPipesCode = _names.genPipeOnDestroy();
|
||||
if (destroyPipesCode.isNotEmpty) {
|
||||
destroyPipesCode = 'if (${destroyPipesParamName}) {${destroyPipesCode}}';
|
||||
}
|
||||
var dehydrateFieldsCode = _names.genDehydrateFields();
|
||||
if (destroyPipesCode.isEmpty && dehydrateFieldsCode.isEmpty) return '';
|
||||
return 'void dehydrateDirectives(${destroyPipesParamName}) '
|
||||
'{ ${destroyPipesCode} ${dehydrateFieldsCode} }';
|
||||
}
|
||||
|
||||
String _maybeGenHydrateDirectives() {
|
||||
var hydrateDirectivesCode = _logic.genHydrateDirectives(_directiveRecords);
|
||||
var hydrateDetectorsCode = _logic.genHydrateDetectors(_directiveRecords);
|
||||
if (hydrateDirectivesCode.isEmpty && hydrateDetectorsCode.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
return 'void hydrateDirectives(directives) '
|
||||
'{ $hydrateDirectivesCode $hydrateDetectorsCode }';
|
||||
}
|
||||
|
||||
/// Generates calls to `onAllChangesDone` for all `Directive`s that request
|
||||
/// them.
|
||||
String _maybeGenCallOnAllChangesDone() {
|
||||
// NOTE(kegluneq): Order is important!
|
||||
var directiveNotifications = _directiveRecords.reversed
|
||||
.where((rec) => rec.callOnAllChangesDone)
|
||||
.map((rec) =>
|
||||
'${_names.getDirectiveName(rec.directiveIndex)}.onAllChangesDone();');
|
||||
|
||||
if (directiveNotifications.isNotEmpty) {
|
||||
return '''
|
||||
void callOnAllChangesDone() {
|
||||
${_names.getDispatcherName()}.notifyOnAllChangesDone();
|
||||
${directiveNotifications.join('')}
|
||||
}
|
||||
''';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _genDeclareFields() {
|
||||
var fields = _names.getAllFieldNames();
|
||||
// If there's only one field, it's `context`, declared in the superclass.
|
||||
if (fields.length == 1) return '';
|
||||
fields.removeAt(CONTEXT_INDEX);
|
||||
var toRemove = 'this.';
|
||||
var declareNames = fields
|
||||
.map((f) => f.startsWith(toRemove) ? f.substring(toRemove.length) : f);
|
||||
return 'var ${declareNames.join(', ')};';
|
||||
}
|
||||
|
||||
String _genRecord(ProtoRecord r) {
|
||||
var rec = null;
|
||||
if (r.isLifeCycleRecord()) {
|
||||
rec = _genDirectiveLifecycle(r);
|
||||
} else if (r.isPipeRecord()) {
|
||||
rec = _genPipeCheck(r);
|
||||
} else {
|
||||
rec = _genReferenceCheck(r);
|
||||
}
|
||||
return '''
|
||||
${this._maybeFirstInBinding(r)}
|
||||
${rec}
|
||||
${this._maybeGenLastInDirective(r)}
|
||||
''';
|
||||
}
|
||||
|
||||
String _genDirectiveLifecycle(ProtoRecord r) {
|
||||
if (r.name == 'onCheck') {
|
||||
return _genOnCheck(r);
|
||||
} else if (r.name == 'onInit') {
|
||||
return _genOnInit(r);
|
||||
} else if (r.name == 'onChange') {
|
||||
return _genOnChange(r);
|
||||
} else {
|
||||
throw new BaseException("Unknown lifecycle event '${r.name}'");
|
||||
}
|
||||
}
|
||||
|
||||
String _genPipeCheck(ProtoRecord r) {
|
||||
var context = _names.getLocalName(r.contextIndex);
|
||||
var argString = r.args.map((arg) => _names.getLocalName(arg)).join(", ");
|
||||
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
|
||||
var pipe = _names.getPipeName(r.selfIndex);
|
||||
var pipeType = r.name;
|
||||
|
||||
var read = '''
|
||||
if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) {
|
||||
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType');
|
||||
}
|
||||
$newValue = $pipe.transform($context, [$argString]);
|
||||
''';
|
||||
|
||||
var check = '''
|
||||
if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) {
|
||||
$newValue = $_UTIL.unwrapValue($newValue);
|
||||
${_genChangeMarker(r)}
|
||||
${_genUpdateDirectiveOrElement(r)}
|
||||
${_genAddToChanges(r)}
|
||||
$oldValue = $newValue;
|
||||
}
|
||||
''';
|
||||
|
||||
return r.shouldBeChecked() ? "${read}${check}" : read;
|
||||
}
|
||||
|
||||
String _genReferenceCheck(ProtoRecord r) {
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
var read = '''
|
||||
${_logic.genPropertyBindingEvalValue(r)}
|
||||
''';
|
||||
|
||||
var check = '''
|
||||
if ($_NOT_IDENTICAL_CHECK_FN($newValue, $oldValue)) {
|
||||
${_genChangeMarker(r)}
|
||||
${_genUpdateDirectiveOrElement(r)}
|
||||
${_genAddToChanges(r)}
|
||||
$oldValue = $newValue;
|
||||
}
|
||||
''';
|
||||
|
||||
var genCode = r.shouldBeChecked() ? "${read}${check}" : read;
|
||||
|
||||
if (r.isPureFunction()) {
|
||||
// Add an "if changed guard"
|
||||
var condition = r.args.map((a) => _names.getChangeName(a)).join(' || ');
|
||||
if (r.isUsedByOtherRecord()) {
|
||||
return 'if ($condition) { $genCode } else { $newValue = $oldValue; }';
|
||||
} else {
|
||||
return 'if ($condition) { $genCode }';
|
||||
}
|
||||
} else {
|
||||
return genCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _genChangeMarker(ProtoRecord r) {
|
||||
return r.argumentToPureFunction
|
||||
? "${this._names.getChangeName(r.selfIndex)} = true;"
|
||||
: "";
|
||||
}
|
||||
|
||||
String _genUpdateDirectiveOrElement(ProtoRecord r) {
|
||||
if (!r.lastInBinding) return '';
|
||||
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
var notifyDebug = _genConfig.logBindingUpdate ? "this.logBindingUpdate(${newValue});" : "";
|
||||
|
||||
var br = r.bindingRecord;
|
||||
if (br.target.isDirective()) {
|
||||
var directiveProperty =
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.target.name}';
|
||||
return '''
|
||||
${_genThrowOnChangeCheck(oldValue, newValue)}
|
||||
$directiveProperty = $newValue;
|
||||
${notifyDebug}
|
||||
$_IS_CHANGED_LOCAL = true;
|
||||
''';
|
||||
} else {
|
||||
return '''
|
||||
${_genThrowOnChangeCheck(oldValue, newValue)}
|
||||
this.notifyDispatcher(${newValue});
|
||||
${notifyDebug}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
||||
String _genThrowOnChangeCheck(String oldValue, String newValue) {
|
||||
if (this._genConfig.genCheckNoChanges) {
|
||||
return '''
|
||||
if(throwOnChange) {
|
||||
this.throwOnChangeError(${oldValue}, ${newValue});
|
||||
}
|
||||
''';
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String _genCheckNoChanges() {
|
||||
if (this._genConfig.genCheckNoChanges) {
|
||||
return 'void checkNoChanges() { runDetectChanges(true); }';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _maybeFirstInBinding(ProtoRecord r) {
|
||||
var prev = ChangeDetectionUtil.protoByIndex(_records, r.selfIndex - 1);
|
||||
var firstInBindng = prev == null || prev.bindingRecord != r.bindingRecord;
|
||||
return firstInBindng && !r.bindingRecord.isDirectiveLifecycle()
|
||||
? "${_names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};"
|
||||
: '';
|
||||
}
|
||||
|
||||
String _genAddToChanges(ProtoRecord r) {
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
if (!r.bindingRecord.callOnChange()) return '';
|
||||
return "$_CHANGES_LOCAL = addChange($_CHANGES_LOCAL, $oldValue, $newValue);";
|
||||
}
|
||||
|
||||
String _maybeGenLastInDirective(ProtoRecord r) {
|
||||
if (!r.lastInDirective) return '';
|
||||
return '''
|
||||
$_CHANGES_LOCAL = null;
|
||||
${_genNotifyOnPushDetectors(r)}
|
||||
$_IS_CHANGED_LOCAL = false;
|
||||
''';
|
||||
}
|
||||
|
||||
String _genOnCheck(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
return 'if (!throwOnChange) '
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.onCheck();';
|
||||
}
|
||||
|
||||
String _genOnInit(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
return 'if (!throwOnChange && !${_names.getAlreadyCheckedName()}) '
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();';
|
||||
}
|
||||
|
||||
String _genOnChange(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
return 'if (!throwOnChange && $_CHANGES_LOCAL != null) '
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}'
|
||||
'.onChange($_CHANGES_LOCAL);';
|
||||
}
|
||||
|
||||
String _genNotifyOnPushDetectors(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
if (!r.lastInDirective || br.isDefaultChangeDetection()) return '';
|
||||
return '''
|
||||
if($_IS_CHANGED_LOCAL) {
|
||||
${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
|
||||
}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
||||
const PROTO_CHANGE_DETECTOR_FACTORY_METHOD = 'newProtoChangeDetector';
|
||||
|
||||
const _BASE_CLASS = '$_GEN_PREFIX.AbstractChangeDetector';
|
||||
const _CHANGES_LOCAL = 'changes';
|
||||
const _GEN_PREFIX = '_gen';
|
||||
const _GEN_RECORDS_METHOD_NAME = '_createRecords';
|
||||
const _IDENTICAL_CHECK_FN = '$_GEN_PREFIX.looseIdentical';
|
||||
const _NOT_IDENTICAL_CHECK_FN = '$_GEN_PREFIX.looseNotIdentical';
|
||||
const _IS_CHANGED_LOCAL = 'isChanged';
|
||||
const _PREGEN_PROTO_CHANGE_DETECTOR_IMPORT =
|
||||
'package:angular2/src/change_detection/pregen_proto_change_detector.dart';
|
||||
const _UTIL = '$_GEN_PREFIX.ChangeDetectionUtil';
|
@ -0,0 +1,25 @@
|
||||
library angular2.transform.template_compiler.compile_step_factory;
|
||||
|
||||
import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/compile_step.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/compile_step_factory.dart'
|
||||
as base;
|
||||
import 'package:angular2/src/render/dom/compiler/directive_parser.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/property_binding_parser.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/text_interpolation_parser.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/view_splitter.dart';
|
||||
|
||||
class CompileStepFactory implements base.CompileStepFactory {
|
||||
final ng.Parser _parser;
|
||||
CompileStepFactory(this._parser);
|
||||
|
||||
List<CompileStep> createSteps(ViewDefinition template) {
|
||||
return [
|
||||
new ViewSplitter(_parser),
|
||||
new PropertyBindingParser(_parser),
|
||||
new DirectiveParser(_parser, template.directives),
|
||||
new TextInterpolationParser(_parser)
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
library angular2.transform.template_compiler.generator;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/change_detection/parser/lexer.dart' as ng;
|
||||
import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
|
||||
import 'package:angular2/src/change_detection/interfaces.dart';
|
||||
import 'package:angular2/src/core/compiler/proto_view_factory.dart';
|
||||
import 'package:angular2/src/dom/dom_adapter.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/style_inliner.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/style_url_resolver.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/view_loader.dart';
|
||||
import 'package:angular2/src/render/dom/schema/element_schema_registry.dart';
|
||||
import 'package:angular2/src/render/dom/schema/dom_element_schema_registry.dart';
|
||||
import 'package:angular2/src/render/dom/template_cloner.dart';
|
||||
import 'package:angular2/src/render/xhr.dart' show XHR;
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/services/url_resolver.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/xhr_impl.dart';
|
||||
import 'package:angular2/src/facade/lang.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'change_detector_codegen.dart' as change;
|
||||
import 'compile_step_factory.dart';
|
||||
import 'reflection/codegen.dart' as reg;
|
||||
import 'reflection/processor.dart' as reg;
|
||||
import 'reflection/reflection_capabilities.dart';
|
||||
import 'view_definition_creator.dart';
|
||||
|
||||
/// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any
|
||||
/// Angular 2 `View` annotations it declares to generate `getter`s,
|
||||
/// `setter`s, and `method`s that would otherwise be reflectively accessed.
|
||||
///
|
||||
/// This method assumes a {@link DomAdapter} has been registered.
|
||||
Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
|
||||
{bool generateRegistrations: true,
|
||||
bool generateChangeDetectors: true, bool reflectPropertiesAsAttributes: false}) async {
|
||||
var viewDefResults = await createViewDefinitions(reader, entryPoint);
|
||||
// Note: TemplateCloner(-1) never serializes Nodes into strings.
|
||||
// we might want to change this to TemplateCloner(0) to force the serialization
|
||||
// later when the transformer also stores the proto view template.
|
||||
var extractor = new _TemplateExtractor(new DomElementSchemaRegistry(),
|
||||
new TemplateCloner(-1), new XhrImpl(reader, entryPoint));
|
||||
|
||||
final processor = new reg.Processor();
|
||||
|
||||
var changeDetectorClasses = new change.Codegen();
|
||||
for (var rType in viewDefResults.viewDefinitions.keys) {
|
||||
var viewDefEntry = viewDefResults.viewDefinitions[rType];
|
||||
var protoView = await extractor.extractTemplates(viewDefEntry.viewDef);
|
||||
if (protoView == null) continue;
|
||||
|
||||
if (generateRegistrations) {
|
||||
processor.process(viewDefEntry, protoView);
|
||||
}
|
||||
if (generateChangeDetectors) {
|
||||
var saved = reflector.reflectionCapabilities;
|
||||
var genConfig = new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), reflectPropertiesAsAttributes);
|
||||
|
||||
reflector.reflectionCapabilities = const NullReflectionCapabilities();
|
||||
var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata,
|
||||
protoView, viewDefEntry.viewDef.directives, genConfig);
|
||||
for (var i = 0; i < defs.length; ++i) {
|
||||
changeDetectorClasses.generate('${rType.typeName}',
|
||||
'_${rType.typeName}_ChangeDetector$i', defs[i]);
|
||||
}
|
||||
reflector.reflectionCapabilities = saved;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kegluneq): Do not hard-code `false` here once i/3436 is fixed.
|
||||
final registrations = new reg.Codegen(generateChangeDetectors: false);
|
||||
registrations.generate(processor);
|
||||
|
||||
var code = viewDefResults.ngDeps.code;
|
||||
if (registrations.isEmpty && changeDetectorClasses.isEmpty) return code;
|
||||
var importInjectIdx =
|
||||
viewDefResults.ngDeps.lib != null ? viewDefResults.ngDeps.lib.end : 0;
|
||||
var codeInjectIdx =
|
||||
viewDefResults.ngDeps.registeredTypes.last.registerMethod.end;
|
||||
var initInjectIdx = viewDefResults.ngDeps.setupMethod.end - 1;
|
||||
return '${code.substring(0, importInjectIdx)}'
|
||||
'${changeDetectorClasses.imports}'
|
||||
'${code.substring(importInjectIdx, codeInjectIdx)}'
|
||||
'${registrations}'
|
||||
'${code.substring(codeInjectIdx, initInjectIdx)}'
|
||||
'${changeDetectorClasses.initialize}'
|
||||
'${code.substring(initInjectIdx)}'
|
||||
'$changeDetectorClasses';
|
||||
}
|
||||
|
||||
/// Extracts `template` and `url` values from `View` annotations, reads
|
||||
/// template code if necessary, and determines what values will be
|
||||
/// reflectively accessed from that template.
|
||||
class _TemplateExtractor {
|
||||
final CompileStepFactory _factory;
|
||||
ViewLoader _loader;
|
||||
ElementSchemaRegistry _schemaRegistry;
|
||||
TemplateCloner _templateCloner;
|
||||
|
||||
_TemplateExtractor(this._schemaRegistry, this._templateCloner, XHR xhr)
|
||||
: _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())) {
|
||||
var urlResolver = new UrlResolver();
|
||||
var styleUrlResolver = new StyleUrlResolver(urlResolver);
|
||||
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
|
||||
|
||||
_loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
|
||||
}
|
||||
|
||||
Future<ProtoViewDto> extractTemplates(ViewDefinition viewDef) async {
|
||||
// Check for "imperative views".
|
||||
if (viewDef.template == null && viewDef.templateAbsUrl == null) return null;
|
||||
|
||||
var templateAndStyles = await _loader.load(viewDef);
|
||||
|
||||
// NOTE(kegluneq): Since this is a global, we must not have any async
|
||||
// operations between saving and restoring it, otherwise we can get into
|
||||
// a bad state. See issue #2359 for additional context.
|
||||
var savedReflectionCapabilities = reflector.reflectionCapabilities;
|
||||
reflector.reflectionCapabilities = const NullReflectionCapabilities();
|
||||
|
||||
var pipeline = new CompilePipeline(_factory.createSteps(viewDef));
|
||||
|
||||
var compileElements = pipeline.processElements(
|
||||
DOM.createTemplate(templateAndStyles.template),
|
||||
ViewType.COMPONENT,
|
||||
viewDef);
|
||||
var protoViewDto = compileElements[0]
|
||||
.inheritedProtoView
|
||||
.build(_schemaRegistry, _templateCloner);
|
||||
|
||||
reflector.reflectionCapabilities = savedReflectionCapabilities;
|
||||
|
||||
return protoViewDto;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
library angular2.transform.template_compiler.reflection.codegen;
|
||||
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/property_utils.dart' as prop;
|
||||
|
||||
import 'model.dart';
|
||||
|
||||
class Codegen {
|
||||
final StringBuffer _buf = new StringBuffer();
|
||||
|
||||
/// Whether we are pre-generating change detectors.
|
||||
/// If we have pre-generated change detectors, we need
|
||||
final bool generateChangeDetectors;
|
||||
|
||||
Codegen({this.generateChangeDetectors});
|
||||
|
||||
void generate(CodegenModel model) {
|
||||
if (model != null) {
|
||||
var calls = _generateGetters(_extractNames(model.getterNames));
|
||||
if (calls.isNotEmpty) {
|
||||
_buf.write('..${REGISTER_GETTERS_METHOD_NAME}'
|
||||
'({${calls.join(', ')}})');
|
||||
}
|
||||
calls = _generateSetters(_extractNames(model.setterNames));
|
||||
if (calls.isNotEmpty) {
|
||||
_buf.write('..${REGISTER_SETTERS_METHOD_NAME}'
|
||||
'({${calls.join(', ')}})');
|
||||
}
|
||||
calls = _generateMethods(_extractNames(model.methodNames));
|
||||
if (calls.isNotEmpty) {
|
||||
_buf.write('..${REGISTER_METHODS_METHOD_NAME}'
|
||||
'({${calls.join(', ')}})');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<String> _extractNames(Iterable<ReflectiveAccessor> accessors) {
|
||||
var names = accessors.where((accessor) {
|
||||
return accessor.isStaticallyNecessary || !generateChangeDetectors;
|
||||
}).map((accessor) => accessor.sanitizedName);
|
||||
var nameList = names.toList();
|
||||
nameList.sort();
|
||||
return nameList;
|
||||
}
|
||||
|
||||
bool get isEmpty => _buf.isEmpty;
|
||||
|
||||
@override
|
||||
String toString() => '$_buf';
|
||||
}
|
||||
|
||||
Iterable<String> _generateGetters(Iterable<String> getterNames) {
|
||||
return getterNames.map((getterName) {
|
||||
if (!prop.isValid(getterName)) {
|
||||
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
|
||||
return prop.lazyInvalidGetter(getterName);
|
||||
} else {
|
||||
return ''' '${prop.sanitize(getterName)}': (o) => o.$getterName''';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Iterable<String> _generateSetters(Iterable<String> setterName) {
|
||||
return setterName.map((setterName) {
|
||||
if (!prop.isValid(setterName)) {
|
||||
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
|
||||
return prop.lazyInvalidSetter(setterName);
|
||||
} else {
|
||||
return ''' '${prop.sanitize(setterName)}': '''
|
||||
''' (o, v) => o.$setterName = v ''';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Iterable<String> _generateMethods(Iterable<String> methodNames) {
|
||||
return methodNames.map((methodName) {
|
||||
if (!prop.isValid(methodName)) {
|
||||
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
|
||||
return prop.lazyInvalidMethod(methodName);
|
||||
} else {
|
||||
return ''' '${prop.sanitize(methodName)}': '''
|
||||
'(o, List args) => Function.apply(o.$methodName, args) ';
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
library angular2.transform.template_compiler.reflection.model;
|
||||
|
||||
import 'package:angular2/src/render/dom/util.dart';
|
||||
|
||||
/// Defines the names of getters, setters, and methods which need to be
|
||||
/// available to Angular 2 via the `reflector` at runtime.
|
||||
/// See [angular2.src.reflection.reflector] for details.
|
||||
abstract class CodegenModel {
|
||||
Iterable<ReflectiveAccessor> get getterNames;
|
||||
Iterable<ReflectiveAccessor> get methodNames;
|
||||
Iterable<ReflectiveAccessor> get setterNames;
|
||||
}
|
||||
|
||||
/// Wraps a getter, setter, or method that we may need to access reflectively in
|
||||
/// an Angular2 app.
|
||||
/// This is essentially a wrapper for `sanitizedName`, which is the name of the
|
||||
/// actual getter, setter, or method that will be registered. Note that
|
||||
/// `operator==` and `hashCode` basically forward to `sanitizedName`.
|
||||
class ReflectiveAccessor {
|
||||
/// The value in the Ast determining that we need this accessor. This is the
|
||||
/// value that is actually present in the template, which may not directly
|
||||
/// correspond to the model on the `Component`.
|
||||
final String astValue;
|
||||
|
||||
/// The sanitized name of this accessor. This is the name of the getter,
|
||||
/// setter, or method on the `Component`.
|
||||
final String sanitizedName;
|
||||
|
||||
/// Whether this getter, setter, or method is still necessary when we have
|
||||
/// pre-generated change detectors.
|
||||
final bool isStaticallyNecessary;
|
||||
|
||||
ReflectiveAccessor(String astValue, {this.isStaticallyNecessary})
|
||||
: this.astValue = astValue,
|
||||
this.sanitizedName = sanitizePropertyName(astValue);
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
if (other is! ReflectiveAccessor) return false;
|
||||
return sanitizedName == other.sanitizedName;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => sanitizedName.hashCode;
|
||||
}
|
||||
|
||||
String sanitizePropertyName(String name) {
|
||||
return dashCaseToCamelCase(EventConfig.parse(name).fieldName);
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
library angular2.transform.template_compiler.reflection.processor;
|
||||
|
||||
import 'package:angular2/src/change_detection/parser/ast.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/transform/template_compiler/view_definition_creator.dart';
|
||||
|
||||
import 'model.dart';
|
||||
|
||||
class Processor implements CodegenModel {
|
||||
/// The names of all requested `getter`s.
|
||||
final Set<ReflectiveAccessor> getterNames = new Set<ReflectiveAccessor>();
|
||||
|
||||
/// The names of all requested `setter`s.
|
||||
final Set<ReflectiveAccessor> setterNames = new Set<ReflectiveAccessor>();
|
||||
|
||||
/// The names of all requested `method`s.
|
||||
final Set<ReflectiveAccessor> methodNames = new Set<ReflectiveAccessor>();
|
||||
|
||||
_NgAstVisitor _visitor;
|
||||
|
||||
Processor() {
|
||||
_visitor = new _NgAstVisitor(this);
|
||||
}
|
||||
|
||||
void process(ViewDefinitionEntry viewDefEntry, ProtoViewDto protoViewDto) {
|
||||
_processViewDefinition(viewDefEntry);
|
||||
_processProtoViewDto(protoViewDto);
|
||||
}
|
||||
|
||||
/// Extracts the names of necessary getters from the events in host and
|
||||
/// dependent [DirectiveMetadata].
|
||||
void _processViewDefinition(ViewDefinitionEntry viewDefEntry) {
|
||||
// These are necessary even with generated change detectors.
|
||||
if (viewDefEntry.hostMetadata != null &&
|
||||
viewDefEntry.hostMetadata.events != null) {
|
||||
viewDefEntry.hostMetadata.events.forEach((eventName) {
|
||||
getterNames.add(
|
||||
new ReflectiveAccessor(eventName, isStaticallyNecessary: true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _processProtoViewDto(ProtoViewDto protoViewDto) {
|
||||
_visitor.isStaticallyNecessary = false;
|
||||
|
||||
protoViewDto.textBindings.forEach((ast) => ast.visit(_visitor));
|
||||
protoViewDto.elementBinders.forEach((binder) {
|
||||
binder.propertyBindings.forEach((binding) {
|
||||
binding.astWithSource.visit(_visitor);
|
||||
setterNames.add(new ReflectiveAccessor(binding.property,
|
||||
isStaticallyNecessary: false));
|
||||
});
|
||||
|
||||
binder.directives.forEach((directiveBinding) {
|
||||
directiveBinding.propertyBindings.values
|
||||
.forEach((propBinding) => propBinding.visit(_visitor));
|
||||
directiveBinding.propertyBindings.keys.forEach((bindingName) {
|
||||
setterNames.add(new ReflectiveAccessor(bindingName,
|
||||
isStaticallyNecessary: false));
|
||||
});
|
||||
|
||||
directiveBinding.hostPropertyBindings.forEach((elementBinding) {
|
||||
elementBinding.astWithSource.visit(_visitor);
|
||||
setterNames.add(new ReflectiveAccessor(elementBinding.property,
|
||||
isStaticallyNecessary: false));
|
||||
});
|
||||
});
|
||||
|
||||
binder.eventBindings
|
||||
.forEach((eventBinding) => eventBinding.source.visit(_visitor));
|
||||
|
||||
binder.directives.forEach((directiveBinding) {
|
||||
directiveBinding.eventBindings
|
||||
.forEach((eventBinding) => eventBinding.source.visit(_visitor));
|
||||
});
|
||||
|
||||
if (binder.nestedProtoView != null) {
|
||||
_processProtoViewDto(binder.nestedProtoView);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _NgAstVisitor extends RecursiveAstVisitor {
|
||||
final Processor _result;
|
||||
|
||||
/// Whether any getters or setters recorded are necessary when running
|
||||
/// statically. A getter or setter that is necessary only for change detection
|
||||
/// is not necessary when running statically because all accesses are handled
|
||||
/// by the dedicated change detector classes.
|
||||
bool isStaticallyNecessary = false;
|
||||
|
||||
_NgAstVisitor(this._result);
|
||||
|
||||
visitMethodCall(MethodCall ast) {
|
||||
_result.methodNames
|
||||
.add(new ReflectiveAccessor(ast.name, isStaticallyNecessary: true));
|
||||
super.visitMethodCall(ast);
|
||||
}
|
||||
|
||||
visitPropertyRead(PropertyRead ast) {
|
||||
_result.getterNames.add(new ReflectiveAccessor(ast.name,
|
||||
isStaticallyNecessary: isStaticallyNecessary));
|
||||
super.visitPropertyRead(ast);
|
||||
}
|
||||
|
||||
visitPropertyWrite(PropertyWrite ast) {
|
||||
_result.setterNames.add(new ReflectiveAccessor(ast.name,
|
||||
isStaticallyNecessary: isStaticallyNecessary));
|
||||
super.visitPropertyWrite(ast);
|
||||
}
|
||||
|
||||
visitSafeMethodCall(SafeMethodCall ast) {
|
||||
_result.methodNames
|
||||
.add(new ReflectiveAccessor(ast.name, isStaticallyNecessary: true));
|
||||
super.visitSafeMethodCall(ast);
|
||||
}
|
||||
|
||||
visitSafePropertyRead(SafePropertyRead ast) {
|
||||
_result.getterNames.add(new ReflectiveAccessor(ast.name,
|
||||
isStaticallyNecessary: isStaticallyNecessary));
|
||||
super.visitSafePropertyRead(ast);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
library angular2.transform.template_compiler.reflection.reflection_capabilities;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'package:angular2/src/reflection/types.dart';
|
||||
|
||||
/// ReflectionCapabilities object that responds to all requests for `getter`s,
|
||||
/// `setter`s, and `method`s with `null`.
|
||||
class NullReflectionCapabilities implements ReflectionCapabilities {
|
||||
const NullReflectionCapabilities();
|
||||
|
||||
_notImplemented(String name) => throw 'Not implemented: $name';
|
||||
|
||||
bool isReflectionEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Function factory(Type type) => _notImplemented("factory");
|
||||
|
||||
List<List> parameters(typeOrFunc) => _notImplemented('parameters');
|
||||
|
||||
List<List> interfaces(typeOrFunc) => _notImplemented('interfaces');
|
||||
|
||||
List annotations(typeOrFunc) => _notImplemented('annotations');
|
||||
|
||||
GetterFn getter(String name) => _nullGetter;
|
||||
|
||||
SetterFn setter(String name) => _nullSetter;
|
||||
|
||||
MethodFn method(String name) => _nullMethod;
|
||||
}
|
||||
|
||||
_nullGetter(Object p) => null;
|
||||
_nullSetter(Object p, v) => null;
|
||||
_nullMethod(Object p, List a) => null;
|
@ -0,0 +1,41 @@
|
||||
library angular2.transform.template_compiler.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/dom/html_adapter.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/options.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'generator.dart';
|
||||
|
||||
/// {@link Transformer} responsible for detecting and processing Angular 2 templates.
|
||||
///
|
||||
/// {@link TemplateCompiler} uses the Angular 2 `Compiler` to process the templates,
|
||||
/// extracting information about what reflection is necessary to render and
|
||||
/// use that template. It then generates code in place of those reflective
|
||||
/// accesses.
|
||||
class TemplateCompiler extends Transformer {
|
||||
final TransformerOptions options;
|
||||
|
||||
TemplateCompiler(this.options);
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
Html5LibDomAdapter.makeCurrent();
|
||||
var id = transform.primaryInput.id;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var transformedCode = formatter.format(await processTemplates(reader, id,
|
||||
generateChangeDetectors: options.generateChangeDetectors,
|
||||
reflectPropertiesAsAttributes: options.reflectPropertiesAsAttributes));
|
||||
transform.addOutput(new Asset.fromString(id, transformedCode));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
library angular2.transform.template_compiler.view_definition_creator;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/ng_deps.dart';
|
||||
import 'package:angular2/src/transform/common/ng_meta.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/assets.dart';
|
||||
|
||||
/// Creates [ViewDefinition] objects for all `View` `Directive`s defined in
|
||||
/// `entryPoint`.
|
||||
Future<ViewDefinitionResults> createViewDefinitions(
|
||||
AssetReader reader, AssetId entryPoint) async {
|
||||
return await new _ViewDefinitionCreator(reader, entryPoint).createViewDefs();
|
||||
}
|
||||
|
||||
class ViewDefinitionResults {
|
||||
final NgDeps ngDeps;
|
||||
final Map<RegisteredType, ViewDefinitionEntry> viewDefinitions;
|
||||
ViewDefinitionResults._(this.ngDeps, this.viewDefinitions);
|
||||
}
|
||||
|
||||
class ViewDefinitionEntry {
|
||||
final RenderDirectiveMetadata hostMetadata;
|
||||
final ViewDefinition viewDef;
|
||||
|
||||
ViewDefinitionEntry._(this.hostMetadata, this.viewDef);
|
||||
}
|
||||
|
||||
String _getComponentId(AssetId assetId, String className) => '$className';
|
||||
|
||||
// TODO(kegluenq): Improve this test.
|
||||
bool _isViewAnnotation(InstanceCreationExpression node) {
|
||||
var constructorName = node.constructorName.type.name;
|
||||
if (constructorName is PrefixedIdentifier) {
|
||||
constructorName = constructorName.identifier;
|
||||
}
|
||||
return constructorName.name == 'View';
|
||||
}
|
||||
|
||||
/// Creates [ViewDefinition] objects for all `View` `Directive`s defined in
|
||||
/// `entryPoint`.
|
||||
class _ViewDefinitionCreator {
|
||||
final AssetReader reader;
|
||||
final AssetId entryPoint;
|
||||
final Future<NgDeps> ngDepsFuture;
|
||||
|
||||
_ViewDefinitionCreator(AssetReader reader, AssetId entryPoint)
|
||||
: this.reader = reader,
|
||||
this.entryPoint = entryPoint,
|
||||
ngDepsFuture = NgDeps.parse(reader, entryPoint);
|
||||
|
||||
Future<ViewDefinitionResults> createViewDefs() async {
|
||||
var ngDeps = await ngDepsFuture;
|
||||
|
||||
var retVal = <RegisteredType, ViewDefinitionEntry>{};
|
||||
var visitor = new _TemplateExtractVisitor(await _extractNgMeta());
|
||||
ngDeps.registeredTypes.forEach((rType) {
|
||||
visitor.reset();
|
||||
rType.annotations.accept(visitor);
|
||||
if (visitor.viewDef != null) {
|
||||
// Note: we use '' because the current file maps to the default prefix.
|
||||
var ngMeta = visitor._metadataMap[''];
|
||||
var typeName = '${rType.typeName}';
|
||||
var hostMetadata = null;
|
||||
if (ngMeta.types.containsKey(typeName)) {
|
||||
hostMetadata = ngMeta.types[typeName];
|
||||
visitor.viewDef.componentId = hostMetadata.id;
|
||||
} else {
|
||||
logger.warning('Missing component "$typeName" in metadata map',
|
||||
asset: entryPoint);
|
||||
visitor.viewDef.componentId = _getComponentId(entryPoint, typeName);
|
||||
}
|
||||
retVal[rType] =
|
||||
new ViewDefinitionEntry._(hostMetadata, visitor.viewDef);
|
||||
}
|
||||
});
|
||||
return new ViewDefinitionResults._(ngDeps, retVal);
|
||||
}
|
||||
|
||||
/// Creates a map from [AssetId] to import prefix for `.dart` libraries
|
||||
/// imported by `entryPoint`, excluding any `.ng_deps.dart` files it imports.
|
||||
/// Unprefixed imports have `null` as their value. `entryPoint` is included
|
||||
/// in the map with no prefix.
|
||||
Future<Map<AssetId, String>> _createImportAssetToPrefixMap() async {
|
||||
// TODO(kegluneq): Support `part` directives.
|
||||
var ngDeps = await ngDepsFuture;
|
||||
|
||||
var importAssetToPrefix = <AssetId, String>{};
|
||||
// Include the `.ng_meta.json` file associated with `entryPoint`.
|
||||
importAssetToPrefix[new AssetId(
|
||||
entryPoint.package, toMetaExtension(entryPoint.path))] = null;
|
||||
|
||||
for (ImportDirective node in ngDeps.imports) {
|
||||
var uri = stringLiteralToString(node.uri);
|
||||
if (uri.endsWith('.dart') && !uri.endsWith(DEPS_EXTENSION)) {
|
||||
var prefix = node.prefix != null && node.prefix.name != null
|
||||
? '${node.prefix.name}'
|
||||
: null;
|
||||
importAssetToPrefix[uriToAssetId(
|
||||
entryPoint, uri, logger, null /* span */,
|
||||
errorOnAbsolute: false)] = prefix;
|
||||
}
|
||||
}
|
||||
return importAssetToPrefix;
|
||||
}
|
||||
|
||||
/// Reads the `.ng_meta.json` files associated with all of `entryPoint`'s
|
||||
/// imports and creates a map `Type` name, prefixed if appropriate to the
|
||||
/// associated [RenderDirectiveMetadata].
|
||||
///
|
||||
/// For example, if in `entryPoint` we have:
|
||||
///
|
||||
/// ```
|
||||
/// import 'component.dart' as prefix;
|
||||
/// ```
|
||||
///
|
||||
/// and in 'component.dart' we have:
|
||||
///
|
||||
/// ```
|
||||
/// @Component(...)
|
||||
/// class MyComponent {...}
|
||||
/// ```
|
||||
///
|
||||
/// This method will look for `component.ng_meta.json`to contain the
|
||||
/// serialized [RenderDirectiveMetadata] for `MyComponent` and any other
|
||||
/// `Directive`s declared in `component.dart`. We use this information to
|
||||
/// build a map:
|
||||
///
|
||||
/// ```
|
||||
/// {
|
||||
/// "prefix.MyComponent": [RenderDirectiveMetadata for MyComponent],
|
||||
/// ...<any other entries>...
|
||||
/// }
|
||||
/// ```
|
||||
Future<Map<String, NgMeta>> _extractNgMeta() async {
|
||||
var importAssetToPrefix = await _createImportAssetToPrefixMap();
|
||||
|
||||
var retVal = <String, NgMeta>{};
|
||||
for (var importAssetId in importAssetToPrefix.keys) {
|
||||
var prefix = importAssetToPrefix[importAssetId];
|
||||
if (prefix == null) prefix = '';
|
||||
var ngMeta = retVal.putIfAbsent(prefix, () => new NgMeta.empty());
|
||||
var metaAssetId = new AssetId(
|
||||
importAssetId.package, toMetaExtension(importAssetId.path));
|
||||
if (await reader.hasInput(metaAssetId)) {
|
||||
try {
|
||||
var json = JSON.decode(await reader.readAsString(metaAssetId));
|
||||
var newMetadata = new NgMeta.fromJson(json);
|
||||
newMetadata.types.forEach((className, metadata) {
|
||||
metadata.id = _getComponentId(importAssetId, className);
|
||||
});
|
||||
ngMeta.addAll(newMetadata);
|
||||
} catch (ex, stackTrace) {
|
||||
logger.warning('Failed to decode: $ex, $stackTrace',
|
||||
asset: metaAssetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor responsible for processing the `annotations` property of a
|
||||
/// [RegisterType] object and pulling out [ViewDefinition] information.
|
||||
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||
ViewDefinition viewDef = null;
|
||||
final Map<String, NgMeta> _metadataMap;
|
||||
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||
|
||||
_TemplateExtractVisitor(this._metadataMap);
|
||||
|
||||
void reset() {
|
||||
viewDef = null;
|
||||
}
|
||||
|
||||
/// These correspond to the annotations themselves.
|
||||
@override
|
||||
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
|
||||
if (_isViewAnnotation(node)) {
|
||||
viewDef = new ViewDefinition(directives: <RenderDirectiveMetadata>[]);
|
||||
node.visitChildren(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// These correspond to the annotation parameters.
|
||||
@override
|
||||
Object visitNamedExpression(NamedExpression node) {
|
||||
// TODO(kegluneq): Remove this limitation.
|
||||
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
|
||||
logger.error(
|
||||
'Angular 2 currently only supports simple identifiers in directives.'
|
||||
' Source: ${node}');
|
||||
return null;
|
||||
}
|
||||
var keyString = '${node.name.label}';
|
||||
if (keyString == 'directives') {
|
||||
_readDirectives(node.expression);
|
||||
}
|
||||
if (keyString == 'template' || keyString == 'templateUrl') {
|
||||
// This could happen in a non-View annotation with a `template` or
|
||||
// `templateUrl` property.
|
||||
if (viewDef == null) return null;
|
||||
|
||||
var valueString = node.expression.accept(_evaluator);
|
||||
if (valueString is! String) {
|
||||
logger.error(
|
||||
'Angular 2 currently only supports string literals in directives.'
|
||||
' Source: ${node}');
|
||||
return null;
|
||||
}
|
||||
if (keyString == 'templateUrl') {
|
||||
if (viewDef.templateAbsUrl != null) {
|
||||
logger.error(
|
||||
'Found multiple values for "templateUrl". Source: ${node}');
|
||||
}
|
||||
viewDef.templateAbsUrl = valueString;
|
||||
} else {
|
||||
// keyString == 'template'
|
||||
if (viewDef.template != null) {
|
||||
logger.error('Found multiple values for "template". Source: ${node}');
|
||||
}
|
||||
viewDef.template = valueString;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _readDirectives(Expression node) {
|
||||
// This could happen in a non-View annotation with a `directives`
|
||||
// parameter.
|
||||
if (viewDef == null) return;
|
||||
|
||||
if (node is! ListLiteral) {
|
||||
logger.error('Angular 2 currently only supports list literals as values '
|
||||
'for "directives". Source: $node');
|
||||
return;
|
||||
}
|
||||
var directiveList = (node as ListLiteral);
|
||||
for (var node in directiveList.elements) {
|
||||
var ngMeta;
|
||||
var name;
|
||||
if (node is SimpleIdentifier) {
|
||||
ngMeta = _metadataMap[''];
|
||||
name = node.name;
|
||||
} else if (node is PrefixedIdentifier) {
|
||||
ngMeta = _metadataMap[node.prefix.name];
|
||||
name = node.name;
|
||||
} else {
|
||||
logger.error(
|
||||
'Angular 2 currently only supports simple and prefixed identifiers '
|
||||
'as values for "directives". Source: $node');
|
||||
return;
|
||||
}
|
||||
if (ngMeta.types.containsKey(name)) {
|
||||
viewDef.directives.add(ngMeta.types[name]);
|
||||
} else if (ngMeta.aliases.containsKey(name)) {
|
||||
viewDef.directives.addAll(ngMeta.flatten(name));
|
||||
} else {
|
||||
logger.warning('Could not find Directive entry for $node. '
|
||||
'Please be aware that Dart transformers have limited support for '
|
||||
'reusable, pre-defined lists of Directives (aka '
|
||||
'"directive aliases"). See https://goo.gl/d8XPt0 for details.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
modules_dart/transform/lib/src/transform/transformer.dart
Normal file
50
modules_dart/transform/lib/src/transform/transformer.dart
Normal file
@ -0,0 +1,50 @@
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
||||
import 'deferred_rewriter/transformer.dart';
|
||||
import 'directive_linker/transformer.dart';
|
||||
import 'directive_metadata_extractor/transformer.dart';
|
||||
import 'directive_processor/transformer.dart';
|
||||
import 'bind_generator/transformer.dart';
|
||||
import 'reflection_remover/transformer.dart';
|
||||
import 'template_compiler/transformer.dart';
|
||||
import 'common/formatter.dart' as formatter;
|
||||
import 'common/options.dart';
|
||||
import 'common/options_reader.dart';
|
||||
|
||||
export 'common/options.dart';
|
||||
|
||||
/// Replaces Angular 2 mirror use with generated code.
|
||||
class AngularTransformerGroup extends TransformerGroup {
|
||||
AngularTransformerGroup._(phases, {bool formatCode: false}) : super(phases) {
|
||||
if (formatCode) {
|
||||
formatter.init(new DartFormatter());
|
||||
}
|
||||
}
|
||||
|
||||
factory AngularTransformerGroup(TransformerOptions options) {
|
||||
var phases = [
|
||||
[new ReflectionRemover(options)],
|
||||
[new DirectiveProcessor(options)]
|
||||
];
|
||||
phases.addAll(new List.generate(
|
||||
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
|
||||
phases.addAll([
|
||||
[
|
||||
new DirectiveLinker(),
|
||||
new DirectiveMetadataExtractor(),
|
||||
new DeferredRewriter(options)
|
||||
],
|
||||
[new BindGenerator(options)],
|
||||
[new TemplateCompiler(options)]
|
||||
]);
|
||||
return new AngularTransformerGroup._(phases,
|
||||
formatCode: options.formatCode);
|
||||
}
|
||||
|
||||
factory AngularTransformerGroup.asPlugin(BarbackSettings settings) {
|
||||
return new AngularTransformerGroup(parseBarbackSettings(settings));
|
||||
}
|
||||
}
|
3
modules_dart/transform/lib/transformer.dart
Normal file
3
modules_dart/transform/lib/transformer.dart
Normal file
@ -0,0 +1,3 @@
|
||||
library angular2.transformer_dart;
|
||||
|
||||
export 'src/transform/transformer.dart';
|
Reference in New Issue
Block a user