208 lines
6.3 KiB
Dart
208 lines
6.3 KiB
Dart
|
library angular2.src.transform.directive_processor;
|
||
|
|
||
|
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:angular2/src/transform/common/ngdata.dart';
|
||
|
import 'package:angular2/src/transform/common/visitor_mixin.dart';
|
||
|
import 'package:path/path.dart' as path;
|
||
|
|
||
|
import 'visitors.dart';
|
||
|
|
||
|
/// Generates a file registering all Angular 2 `Directive`s found in [code] in
|
||
|
/// ngDeps format [TODO(kegluneq): documentation reference needed]. [path] is
|
||
|
/// the path to the file (or 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.
|
||
|
String createNgDeps(String code, String path, {bool forceGenerate: false}) {
|
||
|
// TODO(kegluneq): Shortcut if we can determine that there are no
|
||
|
// [Directive]s present.
|
||
|
var writer = new PrintStringWriter();
|
||
|
var visitor = new CreateNgDepsVisitor(writer, path);
|
||
|
parseCompilationUnit(code, name: path).accept(visitor);
|
||
|
if (visitor.foundNgDirectives || forceGenerate) {
|
||
|
return writer.toString();
|
||
|
} else {
|
||
|
return '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Visitor responsible for processing [CompilationUnit] and creating an
|
||
|
/// associated .ngDeps.dart file.
|
||
|
class CreateNgDepsVisitor extends Object
|
||
|
with SimpleAstVisitor<Object>, VisitorMixin {
|
||
|
final PrintWriter writer;
|
||
|
final _Tester _tester = const _Tester();
|
||
|
bool foundNgDirectives = false;
|
||
|
bool wroteImport = false;
|
||
|
final ToSourceVisitor _copyVisitor;
|
||
|
final FactoryTransformVisitor _factoryVisitor;
|
||
|
final ParameterTransformVisitor _paramsVisitor;
|
||
|
final AnnotationsTransformVisitor _metaVisitor;
|
||
|
final NgData _ngData = new NgData();
|
||
|
|
||
|
/// The path to the file which we are parsing.
|
||
|
final String importPath;
|
||
|
|
||
|
CreateNgDepsVisitor(PrintWriter writer, this.importPath)
|
||
|
: writer = writer,
|
||
|
_copyVisitor = new ToSourceVisitor(writer),
|
||
|
_factoryVisitor = new FactoryTransformVisitor(writer),
|
||
|
_paramsVisitor = new ParameterTransformVisitor(writer),
|
||
|
_metaVisitor = new AnnotationsTransformVisitor(writer);
|
||
|
|
||
|
@override
|
||
|
void visitCompilationUnit(CompilationUnit node) {
|
||
|
visitNodeListWithSeparator(node.directives, " ");
|
||
|
_openFunctionWrapper();
|
||
|
visitNodeListWithSeparator(node.declarations, " ");
|
||
|
_closeFunctionWrapper();
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
void _writeImport() {
|
||
|
writer.print('import \'${path.basename(importPath)}\';');
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Object visitImportDirective(ImportDirective node) {
|
||
|
if (!wroteImport) {
|
||
|
_writeImport();
|
||
|
wroteImport = true;
|
||
|
}
|
||
|
_ngData.imports.add(node.uri.stringValue);
|
||
|
return node.accept(_copyVisitor);
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Object visitExportDirective(ExportDirective node) {
|
||
|
_ngData.imports.add(node.uri.stringValue);
|
||
|
return node.accept(_copyVisitor);
|
||
|
}
|
||
|
|
||
|
void _openFunctionWrapper() {
|
||
|
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
|
||
|
_ngData.importOffset = writer.toString().length;
|
||
|
writer.print('bool _visited = false;'
|
||
|
'void ${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME}) {'
|
||
|
'if (_visited) return; _visited = true;');
|
||
|
}
|
||
|
|
||
|
void _closeFunctionWrapper() {
|
||
|
if (foundNgDirectives) {
|
||
|
writer.print(';');
|
||
|
}
|
||
|
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
|
||
|
_ngData.registerOffset = writer.toString().length;
|
||
|
writer.print('}');
|
||
|
writer.print('// ${_ngData.toJson()}');
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
var shouldProcess = node.metadata.any(_tester._isDirective);
|
||
|
|
||
|
if (shouldProcess) {
|
||
|
var ctor = _getCtor(node);
|
||
|
|
||
|
if (!foundNgDirectives) {
|
||
|
// The receiver for cascaded calls.
|
||
|
writer.print(REFLECTOR_VAR_NAME);
|
||
|
foundNgDirectives = true;
|
||
|
}
|
||
|
writer.print('..registerType(');
|
||
|
visitNode(node.name);
|
||
|
writer.print(', {"factory": ');
|
||
|
if (ctor == null) {
|
||
|
_generateEmptyFactory(node.name.toString());
|
||
|
} else {
|
||
|
ctor.accept(_factoryVisitor);
|
||
|
}
|
||
|
writer.print(', "parameters": ');
|
||
|
if (ctor == null) {
|
||
|
_generateEmptyParams();
|
||
|
} else {
|
||
|
ctor.accept(_paramsVisitor);
|
||
|
}
|
||
|
writer.print(', "annotations": ');
|
||
|
node.accept(_metaVisitor);
|
||
|
writer.print('})');
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Object _nodeToSource(AstNode node) {
|
||
|
if (node == null) return null;
|
||
|
return node.accept(_copyVisitor);
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Object visitLibraryDirective(LibraryDirective node) => _nodeToSource(node);
|
||
|
|
||
|
@override
|
||
|
Object visitPartOfDirective(PartOfDirective node) {
|
||
|
// TODO(kegluneq): Consider importing [node.libraryName].
|
||
|
logger.warning('[${importPath}]: '
|
||
|
'Found `part of` directive while generating ${DEPS_EXTENSION} file, '
|
||
|
'Transform may fail due to missing imports in generated file.');
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Object visitPrefixedIdentifier(PrefixedIdentifier node) =>
|
||
|
_nodeToSource(node);
|
||
|
|
||
|
@override
|
||
|
Object visitSimpleIdentifier(SimpleIdentifier node) => _nodeToSource(node);
|
||
|
}
|
||
|
|
||
|
class _Tester {
|
||
|
const _Tester();
|
||
|
|
||
|
bool _isDirective(Annotation meta) {
|
||
|
var metaName = meta.name.toString();
|
||
|
return metaName == 'Component' ||
|
||
|
metaName == 'Decorator' ||
|
||
|
metaName == 'Template';
|
||
|
}
|
||
|
}
|