feat(dart/transform): Add simple ParseTemplates step
Adds a step that parses `inline` Template values to generate getters and setters.
This commit is contained in:
		
							parent
							
								
									5d502d4093
								
							
						
					
					
						commit
						b3fa1fa4fa
					
				
							
								
								
									
										133
									
								
								modules/angular2/src/transform/template_parser/generator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								modules/angular2/src/transform/template_parser/generator.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | ||||
| library angular2.src.transform.template_parser.generator; | ||||
| 
 | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:analyzer/analyzer.dart'; | ||||
| import 'package:angular2/src/change_detection/parser/ast.dart'; | ||||
| 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/core/compiler/pipeline/compile_element.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/compile_pipeline.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/compile_step.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/property_binding_parser.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/text_interpolation_parser.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/view_splitter.dart'; | ||||
| import 'package:angular2/src/dom/dom_adapter.dart'; | ||||
| import 'package:angular2/src/dom/html5lib_adapter.dart'; | ||||
| import 'package:angular2/src/reflection/reflection.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/parser.dart'; | ||||
| import 'package:barback/barback.dart'; | ||||
| import 'package:code_transformers/assets.dart'; | ||||
| 
 | ||||
| import 'recording_reflection_capabilities.dart'; | ||||
| 
 | ||||
| Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async { | ||||
|   var parser = new Parser(reader); | ||||
|   NgDeps ngDeps = await parser.parse(entryPoint); | ||||
| 
 | ||||
|   var registrations = new StringBuffer(); | ||||
|   ngDeps.registeredTypes.forEach((rType) { | ||||
|     _processRegisteredType(reader, rType).forEach((String templateText) { | ||||
|       var values = _processTemplate(templateText); | ||||
|       var calls = _generateGetters('${rType.typeName}', values.getterNames); | ||||
|       if (calls.isNotEmpty) { | ||||
|         registrations.write('..registerGetters({${calls.join(', ')}})'); | ||||
|       } | ||||
|       calls = _generateSetters('${rType.typeName}', values.setterNames); | ||||
|       if (calls.isNotEmpty) { | ||||
|         registrations.write('..registerSetters({${calls.join(', ')}})'); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   String code = ngDeps.code; | ||||
|   if (registrations.length == 0) return code; | ||||
|   var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end; | ||||
|   return '${code.substring(0, codeInjectIdx)}' | ||||
|       '${registrations}' | ||||
|       '${code.substring(codeInjectIdx)}'; | ||||
| } | ||||
| 
 | ||||
| RecordingReflectionCapabilities _processTemplate(String templateCode) { | ||||
|   var recordingCapabilities = new RecordingReflectionCapabilities(); | ||||
|   reflector.reflectionCapabilities = recordingCapabilities; | ||||
| 
 | ||||
|   var compilePipeline = new CompilePipeline(createCompileSteps()); | ||||
|   var template = DOM.createTemplate(templateCode); | ||||
|   // TODO(kegluneq): Need to parse this from a file when not inline. | ||||
|   compilePipeline.process(template, templateCode); | ||||
| 
 | ||||
|   return recordingCapabilities; | ||||
| } | ||||
| 
 | ||||
| List<String> _generateGetters(String typeName, List<String> getterNames) { | ||||
|   var getters = []; | ||||
|   getterNames.forEach((prop) { | ||||
|     // TODO(kegluneq): Include `typeName` where possible. | ||||
|     getters.add('\'$prop\': (o) => o.$prop'); | ||||
|   }); | ||||
|   return getters; | ||||
| } | ||||
| 
 | ||||
| List<String> _generateSetters(String typeName, List<String> setterName) { | ||||
|   var setters = []; | ||||
|   setterName.forEach((prop) { | ||||
|     // TODO(kegluneq): Include `typeName` where possible. | ||||
|     setters.add('\'$prop\': (o, String v) => o.$prop = v'); | ||||
|   }); | ||||
|   return setters; | ||||
| } | ||||
| 
 | ||||
| List<CompileStep> createCompileSteps() { | ||||
|   var parser = new ng.Parser(new ng.Lexer()); | ||||
|   return [ | ||||
|     new ViewSplitter(parser), | ||||
| //    cssProcessor.getCompileStep( | ||||
| //    compiledComponent, shadowDomStrategy, templateUrl), | ||||
|     new PropertyBindingParser(parser), | ||||
| //    new DirectiveParser(directives), | ||||
|     new TextInterpolationParser(parser) | ||||
| //    new ElementBindingMarker(), | ||||
| //    new ProtoViewBuilder(changeDetection, shadowDomStrategy), | ||||
| //    new ProtoElementInjectorBuilder(), | ||||
| //    new ElementBinderBuilder(parser) | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| List<String> _processRegisteredType(AssetReader reader, RegisteredType t) { | ||||
|   var visitor = new _TemplateExtractVisitor(reader); | ||||
|   t.annotations.accept(visitor); | ||||
|   return visitor.templateText; | ||||
| } | ||||
| 
 | ||||
| class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> { | ||||
|   final List<String> templateText = []; | ||||
|   final AssetReader _reader; | ||||
| 
 | ||||
|   _TemplateExtractVisitor(this._reader); | ||||
| 
 | ||||
|   @override | ||||
|   Object visitNamedExpression(NamedExpression node) { | ||||
|     // TODO(kegluneq): Remove this limitation. | ||||
|     if (node.name is Label && node.name.label is SimpleIdentifier) { | ||||
|       var keyString = '${node.name.label}'; | ||||
|       if (keyString == 'inline') { | ||||
|         if (node.expression is SimpleStringLiteral) { | ||||
|           templateText.add(stringLiteralToString(node.expression)); | ||||
|         } else { | ||||
|           logger.error( | ||||
|               'Angular 2 currently only supports string literals in directives', | ||||
|               ' Source: ${node}'); | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       logger.error( | ||||
|           'Angular 2 currently only supports simple identifiers in directives', | ||||
|           ' Source: ${node}'); | ||||
|     } | ||||
|     return super.visitNamedExpression(node); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| library angular2.src.transform.template_parser.recording_reflection_capabilities; | ||||
| 
 | ||||
| import 'package:angular2/src/reflection/reflection_capabilities.dart'; | ||||
| import 'package:angular2/src/reflection/types.dart'; | ||||
| 
 | ||||
| class RecordingReflectionCapabilities implements ReflectionCapabilities { | ||||
|   void _notImplemented(String name) { | ||||
|     throw 'Not implemented: $name'; | ||||
|   } | ||||
| 
 | ||||
|   final List<String> getterNames = []; | ||||
|   final List<String> setterNames = []; | ||||
|   final List<String> methodNames = []; | ||||
| 
 | ||||
|   Function factory(Type type) => _notImplemented('factory'); | ||||
| 
 | ||||
|   List<List> parameters(typeOrFunc) => _notImplemented('parameters'); | ||||
| 
 | ||||
|   List annotations(typeOrFunc) => _notImplemented('annotations'); | ||||
| 
 | ||||
|   static GetterFn _nullGetter = (Object p) => null; | ||||
|   static SetterFn _nullSetter = (Object p, v) => null; | ||||
|   static MethodFn _nullMethod = (Object p, List a) => null; | ||||
| 
 | ||||
|   GetterFn getter(String name) { | ||||
|     getterNames.add(name); | ||||
|     return _nullGetter; | ||||
|   } | ||||
| 
 | ||||
|   SetterFn setter(String name) { | ||||
|     setterNames.add(name); | ||||
|     return _nullSetter; | ||||
|   } | ||||
| 
 | ||||
|   MethodFn method(String name) { | ||||
|     methodNames.add(name); | ||||
|     return _nullMethod; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,65 @@ | ||||
| library angular2.src.transform.template_parser.transformer; | ||||
| 
 | ||||
| import 'dart:async'; | ||||
| import 'package:angular2/src/change_detection/parser/ast.dart'; | ||||
| import 'package:angular2/src/change_detection/parser/lexer.dart'; | ||||
| import 'package:angular2/src/change_detection/parser/parser.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/compile_element.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/compile_pipeline.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/compile_step.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/property_binding_parser.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/text_interpolation_parser.dart'; | ||||
| import 'package:angular2/src/core/compiler/pipeline/view_splitter.dart'; | ||||
| import 'package:angular2/src/dom/dom_adapter.dart'; | ||||
| import 'package:angular2/src/dom/html5lib_adapter.dart'; | ||||
| import 'package:angular2/src/reflection/reflection.dart'; | ||||
| import 'package:angular2/src/reflection/reflection_capabilities.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 'package:html5lib/dom.dart' as html; | ||||
| import 'package:html5lib/parser.dart' as html; | ||||
| import 'package:path/path.dart' as path; | ||||
| 
 | ||||
| import 'generator.dart'; | ||||
| 
 | ||||
| class TemplateParser extends Transformer { | ||||
|   final TransformerOptions options; | ||||
| 
 | ||||
|   TemplateParser(this.options); | ||||
| 
 | ||||
|   @override | ||||
|   bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION); | ||||
| 
 | ||||
|   @override | ||||
|   Future apply(Transform transform) async { | ||||
|     log.init(transform); | ||||
| 
 | ||||
|     Html5LibDomAdapter.makeCurrent(); | ||||
| 
 | ||||
|     try { | ||||
| 
 | ||||
| //      var doc = html.parse(inlineTemplate); | ||||
| //      parseTemplate(doc); | ||||
| 
 | ||||
|       var id = transform.primaryInput.id; | ||||
|       var reader = new AssetReader.fromTransform(transform); | ||||
|       var transformedCode = await processTemplates(reader, id); | ||||
|       transform.addOutput(new Asset.fromString(id, transformedCode)); | ||||
|     } catch (ex, stackTrace) { | ||||
|       log.logger.error('Parsing ng templates failed.\n' | ||||
|           'Exception: $ex\n' | ||||
|           'Stack Trace: $stackTrace'); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const inlineTemplate = | ||||
|     '''<div class=\"greeting\">{{greeting}} <span red>world</span>!</div> | ||||
| <button class=\"changeButton\" (click)=\"changeGreeting()\"> | ||||
| change greeting | ||||
| </button> | ||||
| '''; | ||||
| @ -7,6 +7,7 @@ import 'directive_linker/transformer.dart'; | ||||
| import 'directive_processor/transformer.dart'; | ||||
| import 'bind_generator/transformer.dart'; | ||||
| import 'reflection_remover/transformer.dart'; | ||||
| import 'template_parser/transformer.dart'; | ||||
| import 'common/formatter.dart' as formatter; | ||||
| import 'common/options.dart'; | ||||
| 
 | ||||
| @ -18,7 +19,8 @@ class AngularTransformerGroup extends TransformerGroup { | ||||
|   AngularTransformerGroup(TransformerOptions options) : super([ | ||||
|         [new DirectiveProcessor(options)], | ||||
|         [new DirectiveLinker(options)], | ||||
|         [new BindGenerator(options), new ReflectionRemover(options)] | ||||
|         [new BindGenerator(options), new ReflectionRemover(options)], | ||||
|         [new TemplateParser(options)] | ||||
|       ]) { | ||||
|     formatter.init(new DartFormatter()); | ||||
|   } | ||||
|  | ||||
| @ -0,0 +1,30 @@ | ||||
| library angular2.test.transform.directive_processor.all_tests; | ||||
| 
 | ||||
| import 'dart:io'; | ||||
| import 'package:barback/barback.dart'; | ||||
| import 'package:angular2/src/transform/common/asset_reader.dart'; | ||||
| import 'package:angular2/src/transform/common/formatter.dart'; | ||||
| import 'package:angular2/src/transform/template_parser/generator.dart'; | ||||
| import 'package:code_transformers/tests.dart'; | ||||
| import 'package:dart_style/dart_style.dart'; | ||||
| import 'package:path/path.dart' as path; | ||||
| import 'package:unittest/unittest.dart'; | ||||
| import 'package:unittest/vm_config.dart'; | ||||
| 
 | ||||
| import '../common/read_file.dart'; | ||||
| 
 | ||||
| var formatter = new DartFormatter(); | ||||
| 
 | ||||
| void allTests() { | ||||
|   AssetReader reader = new TestAssetReader(); | ||||
| 
 | ||||
|   test('should parse simple inline templates.', () async { | ||||
|     var inputPath = 'template_parser/basic_files/hello.ngDeps.dart'; | ||||
|     var expected = | ||||
|         readFile('template_parser/basic_files/expected/hello.ngDeps.dart'); | ||||
|     var output = await processTemplates(reader, new AssetId('a', inputPath)); | ||||
|     output = formatter.format(output); | ||||
|     expected = formatter.format(expected); | ||||
|     expect(output, equals(expected)); | ||||
|   }); | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| library examples.src.hello_world.index_common_dart; | ||||
| 
 | ||||
| import 'hello.dart'; | ||||
| import 'package:angular2/angular2.dart' | ||||
|     show bootstrap, Component, Decorator, Template, NgElement; | ||||
| 
 | ||||
| bool _visited = false; | ||||
| void setupReflection(reflector) { | ||||
|   if (_visited) return; | ||||
|   _visited = true; | ||||
|   reflector | ||||
|     ..registerType(HelloCmp, { | ||||
|       'factory': () => new HelloCmp(), | ||||
|       'parameters': const [const []], | ||||
|       'annotations': const [ | ||||
|         const Component(selector: 'hello-app'), | ||||
|         const Template(inline: '{{greeting}}') | ||||
|       ] | ||||
|     }) | ||||
|     ..registerGetters({'greeting': (o) => o.greeting}) | ||||
|     ..registerSetters({'greeting': (o, String v) => o.greeting = v}); | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| library examples.src.hello_world.index_common_dart; | ||||
| 
 | ||||
| import 'hello.dart'; | ||||
| import 'package:angular2/angular2.dart' | ||||
|     show bootstrap, Component, Decorator, Template, NgElement; | ||||
| 
 | ||||
| bool _visited = false; | ||||
| void setupReflection(reflector) { | ||||
|   if (_visited) return; | ||||
|   _visited = true; | ||||
|   reflector | ||||
|     ..registerType(HelloCmp, { | ||||
|       'factory': () => new HelloCmp(), | ||||
|       'parameters': const [const []], | ||||
|       'annotations': const [ | ||||
|         const Component(selector: 'hello-app'), | ||||
|         const Template(inline: '{{greeting}}') | ||||
|       ] | ||||
|     }); | ||||
| } | ||||
| @ -7,6 +7,7 @@ import 'bind_generator/all_tests.dart' as bindGenerator; | ||||
| import 'directive_processor/all_tests.dart' as directiveProcessor; | ||||
| import 'integration/all_tests.dart' as integration; | ||||
| import 'reflection_remover/all_tests.dart' as reflectionRemover; | ||||
| import 'template_parser/all_tests.dart' as templateParser; | ||||
| 
 | ||||
| main() { | ||||
|   useVMConfiguration(); | ||||
| @ -14,4 +15,5 @@ main() { | ||||
|   group('Directive Processor', directiveProcessor.allTests); | ||||
|   group('Reflection Remover', reflectionRemover.allTests); | ||||
|   group('Transformer Pipeline', integration.allTests); | ||||
|   group('Template Parser', templateParser.allTests); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user