feat(dart/transform): Detect annotations which extend Injectable or Template.

Create a method that recursively walks imports from an entry point and
determines where classes are registered.

Use this information to determine if a particular annotation implements or
extends Injectable or Template.
This commit is contained in:
Jacob MacDonald
2015-03-19 15:47:10 -07:00
parent 6600ac7031
commit c65fd31e86
12 changed files with 286 additions and 29 deletions

View File

@ -0,0 +1,86 @@
library angular2.src.transform.common.classdef_parser;
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:barback/barback.dart';
import 'package:code_transformers/assets.dart';
/// Creates a mapping of [AssetId]s to the [ClassDeclaration]s which they
/// define.
Future<Map<AssetId, List<ClassDeclaration>>> createTypeMap(
AssetReader reader, AssetId id) {
return _recurse(reader, id);
}
Future<Map<AssetId, List<ClassDeclaration>>> _recurse(
AssetReader reader, AssetId id,
[_ClassDefVisitor visitor, Set<AssetId> seen]) async {
if (seen == null) seen = new Set<AssetId>();
if (visitor == null) visitor = new _ClassDefVisitor();
if (seen.contains(id)) return visitor.result;
seen.add(id);
var hasAsset = await reader.hasInput(id);
if (!hasAsset) return visitor.result;
var code = await reader.readAsString(id);
visitor.current = id;
parseCompilationUnit(code,
name: id.path,
parseFunctionBodies: false,
suppressErrors: true).accept(visitor);
var toWait = [];
visitor.dependencies[id]
.map((node) => stringLiteralToString(node.uri))
.where(_isNotDartImport)
.forEach((uri) {
var nodeId = uriToAssetId(id, uri, logger, null);
toWait.add(_recurse(reader, nodeId, visitor, seen));
});
await Future.wait(toWait);
return visitor.result;
}
bool _isNotDartImport(String uri) => !uri.startsWith('dart:');
class _ClassDefVisitor extends Object with RecursiveAstVisitor<Object> {
final Map<AssetId, List<ClassDeclaration>> result = {};
final Map<AssetId, List<NamespaceDirective>> dependencies = {};
List<ClassDeclaration> _currentClass;
List<NamespaceDirective> _currentDependencies;
void set current(AssetId val) {
_currentDependencies = dependencies.putIfAbsent(val, () => []);
_currentClass = result.putIfAbsent(val, () => []);
}
// TODO(kegluneq): Handle `part` directives.
@override
Object visitPartDirective(PartDirective node) => null;
@override
Object visitImportDirective(ImportDirective node) {
_currentDependencies.add(node);
return null;
}
@override
Object visitExportDirective(ExportDirective node) {
_currentDependencies.add(node);
return null;
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) => null;
@override
Object visitClassDeclaration(ClassDeclaration node) {
_currentClass.add(node);
return null;
}
}

View File

@ -4,6 +4,7 @@ import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:barback/barback.dart' show AssetId;
import 'package:path/path.dart' as path;
import 'visitors.dart';
@ -15,11 +16,12 @@ import 'visitors.dart';
/// If no Angular 2 `Directive`s are found in [code], returns the empty
/// string unless [forceGenerate] is true, in which case an empty ngDeps
/// file is created.
String createNgDeps(String code, String path) {
String createNgDeps(String code, String path,
Map<AssetId, List<ClassDeclaration>> assetClasses) {
// TODO(kegluneq): Shortcut if we can determine that there are no
// [Directive]s present, taking into account `export`s.
var writer = new PrintStringWriter();
var visitor = new CreateNgDepsVisitor(writer, path);
var visitor = new CreateNgDepsVisitor(writer, path, assetClasses);
parseCompilationUnit(code, name: path).accept(visitor);
return '$writer';
}
@ -28,7 +30,7 @@ String createNgDeps(String code, String path) {
/// associated .ng_deps.dart file.
class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
final PrintWriter writer;
final _Tester _tester = const _Tester();
final _Tester _tester;
bool _foundNgDirectives = false;
bool _wroteImport = false;
final ToSourceVisitor _copyVisitor;
@ -39,12 +41,14 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
/// The path to the file which we are parsing.
final String importPath;
CreateNgDepsVisitor(PrintWriter writer, this.importPath)
CreateNgDepsVisitor(PrintWriter writer, this.importPath,
Map<AssetId, List<ClassDeclaration>> assetClasses)
: writer = writer,
_copyVisitor = new ToSourceVisitor(writer),
_factoryVisitor = new FactoryTransformVisitor(writer),
_paramsVisitor = new ParameterTransformVisitor(writer),
_metaVisitor = new AnnotationsTransformVisitor(writer);
_metaVisitor = new AnnotationsTransformVisitor(writer),
_tester = new _Tester(assetClasses);
void _visitNodeListWithSeparator(NodeList<AstNode> list, String separator) {
if (list == null) return;
@ -136,7 +140,7 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
@override
Object visitClassDeclaration(ClassDeclaration node) {
var shouldProcess = node.metadata.any(_tester._isDirective);
var shouldProcess = node.metadata.any(_tester._shouldKeepMeta);
if (shouldProcess) {
var ctor = _getCtor(node);
@ -199,15 +203,40 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
Object visitSimpleIdentifier(SimpleIdentifier node) => _nodeToSource(node);
}
class _Tester {
const _Tester();
const annotationNamesToKeep = const ['Injectable', 'Template'];
bool _isDirective(Annotation meta) {
var metaName = meta.name.toString();
return metaName == 'Component' ||
metaName == 'Decorator' ||
metaName == 'Injectable' ||
metaName == 'View' ||
metaName == 'Viewport';
class _Tester {
final Map<String, ClassDeclaration> _classesByName;
_Tester(Map<AssetId, List<ClassDeclaration>> assetClasses)
: _classesByName = new Map.fromIterables(assetClasses.values
.expand((classes) => classes.map((c) => c.name.toString())),
assetClasses.values.expand((list) => list));
bool _shouldKeepMeta(Annotation meta) =>
_shouldKeepClass(_classesByName[meta.name.name]);
bool _shouldKeepClass(ClassDeclaration next) {
while (next != null) {
if (annotationNamesToKeep.contains(next.name.name)) return true;
// Check classes that this class implements.
if (next.implementsClause != null) {
for (var interface in next.implementsClause.interfaces) {
if (_shouldKeepClass(_classesByName[interface.name.name])) {
return true;
}
}
}
// Check the class that this class extends.
if (next.extendsClause != null && next.extendsClause.superclass != null) {
next = _classesByName[next.extendsClause.superclass.name.name];
} else {
break;
}
}
return false;
}
}

View File

@ -2,6 +2,8 @@ library angular2.transform.directive_processor.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/classdef_parser.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';
@ -32,8 +34,10 @@ class DirectiveProcessor extends Transformer {
try {
var asset = transform.primaryInput;
var reader = new AssetReader.fromTransform(transform);
var defMap = await createTypeMap(reader, asset.id);
var assetCode = await asset.readAsString();
var ngDepsSrc = createNgDeps(assetCode, asset.id.path);
var ngDepsSrc = createNgDeps(assetCode, asset.id.path, defMap);
if (ngDepsSrc != null && ngDepsSrc.isNotEmpty) {
var ngDepsAssetId =
transform.primaryInput.id.changeExtension(DEPS_EXTENSION);