| Index: third_party/pkg/angular/test/tools/transformer/metadata_generator_spec.dart
|
| diff --git a/third_party/pkg/angular/test/tools/transformer/metadata_generator_spec.dart b/third_party/pkg/angular/test/tools/transformer/metadata_generator_spec.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cd733fd5d963632601541faeb5d114c36bab996e
|
| --- /dev/null
|
| +++ b/third_party/pkg/angular/test/tools/transformer/metadata_generator_spec.dart
|
| @@ -0,0 +1,751 @@
|
| +library angular.test.tools.transformer.metadata_generator_spec;
|
| +
|
| +import 'dart:async';
|
| +
|
| +import 'package:angular/tools/transformer/options.dart';
|
| +import 'package:angular/tools/transformer/metadata_generator.dart';
|
| +import 'package:barback/barback.dart';
|
| +import 'package:code_transformers/resolver.dart';
|
| +import 'package:code_transformers/tests.dart' as tests;
|
| +
|
| +import '../../jasmine_syntax.dart';
|
| +
|
| +main() {
|
| + describe('MetadataGenerator', () {
|
| + var options = new TransformOptions(sdkDirectory: dartSdkDirectory);
|
| +
|
| + var resolvers = new Resolvers(dartSdkDirectory);
|
| +
|
| + var phases = [
|
| + [new MetadataGenerator(options, resolvers)]
|
| + ];
|
| +
|
| + it('should extract member metadata', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Decorator(selector: r'[*=/{{.*}}/]')
|
| + class Engine {
|
| + @NgOneWay('another-expression')
|
| + String anotherExpression;
|
| +
|
| + @NgCallback('callback')
|
| + set callback(Function) {}
|
| +
|
| + set twoWayStuff(String abc) {}
|
| + @NgTwoWay('two-way-stuff')
|
| + String get twoWayStuff => null;
|
| + }
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(selector: r\'[*=/{{.*}}/]\', '
|
| + 'map: const {'
|
| + '\'another-expression\': \'=>anotherExpression\', '
|
| + '\'callback\': \'&callback\', '
|
| + '\'two-way-stuff\': \'<=>twoWayStuff\''
|
| + '})',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should extract member metadata from superclass', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + class Engine {
|
| + @NgOneWay('another-expression')
|
| + String anotherExpression;
|
| +
|
| + @NgCallback('callback')
|
| + set callback(Function) {}
|
| +
|
| + set twoWayStuff(String abc) {}
|
| + @NgTwoWay('two-way-stuff')
|
| + String get twoWayStuff => null;
|
| + }
|
| +
|
| + @Decorator(selector: r'[*=/{{.*}}/]')
|
| + class InternalCombustionEngine extends Engine {
|
| + @NgOneWay('ice-expression')
|
| + String iceExpression;
|
| + }
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.InternalCombustionEngine': [
|
| + 'const import_1.Decorator(selector: r\'[*=/{{.*}}/]\', '
|
| + 'map: const {'
|
| + '\'ice-expression\': \'=>iceExpression\', '
|
| + '\'another-expression\': \'=>anotherExpression\', '
|
| + '\'callback\': \'&callback\', '
|
| + '\'two-way-stuff\': \'<=>twoWayStuff\''
|
| + '})',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should warn on multiple annotations', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @DummyAnnotation("parse attribute annotations")
|
| + class Engine {
|
| + @NgCallback('callback')
|
| + @NgOneWay('another-expression')
|
| + set callback(Function) {}
|
| + }
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.DummyAnnotation("parse attribute annotations")',
|
| + ]
|
| + },
|
| + messages: ['warning: callback can only have one annotation. '
|
| + '(web/main.dart 4 18)']);
|
| + });
|
| +
|
| + it('should warn on duplicated annotations', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Decorator(map: {'another-expression': '=>anotherExpression'})
|
| + class Engine {
|
| + @NgOneWay('another-expression')
|
| + set anotherExpression(Function) {}
|
| + }
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(map: const {'
|
| + '\'another-expression\': \'=>anotherExpression\'})',
|
| + ]
|
| + },
|
| + messages: ['warning: Directive @NgOneWay(\'another-expression\') '
|
| + 'already contains an entry for \'another-expression\' '
|
| + '(web/main.dart 2 16)'
|
| + ]);
|
| + });
|
| +
|
| + it('should merge member annotations', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Directive(
|
| + selector: 'first',
|
| + map: {'first-expression': '=>anotherExpression'})
|
| + @Directive(
|
| + selector: 'second',
|
| + map: {'second-expression': '=>anotherExpression'})
|
| + class Engine {
|
| + set anotherExpression(Function) {}
|
| +
|
| + set twoWayStuff(String abc) {}
|
| + @NgTwoWay('two-way-stuff')
|
| + String get twoWayStuff => null;
|
| + }
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Directive(selector: \'first\', '
|
| + 'map: const {'
|
| + '\'first-expression\': \'=>anotherExpression\', '
|
| + '\'two-way-stuff\': \'<=>twoWayStuff\'})',
|
| + 'const import_1.Directive(selector: \'second\', '
|
| + 'map: const {'
|
| + '\'second-expression\': \'=>anotherExpression\', '
|
| + '\'two-way-stuff\': \'<=>twoWayStuff\'})',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should warn on multiple annotations (across getter/setter)', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @DummyAnnotation("parse attribute annotations")
|
| + class Engine {
|
| + @NgCallback('callback')
|
| + set callback(Function) {}
|
| +
|
| + @NgOneWay('another-expression')
|
| + get callback => null;
|
| + }
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.DummyAnnotation("parse attribute annotations")',
|
| + ]
|
| + },
|
| + messages: ['warning: callback can only have one annotation. '
|
| + '(web/main.dart 4 18)']);
|
| + });
|
| +
|
| + it('should extract map arguments', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Decorator(map: const {'ng-value': '&ngValue', 'key': 'value'})
|
| + class Engine {}
|
| +
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(map: const {\'ng-value\': '
|
| + '\'&ngValue\', \'key\': \'value\'})',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should extract list arguments', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Decorator(exportExpressions: ['one', 'two'])
|
| + class Engine {}
|
| +
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + "const import_1.Decorator(exportExpressions: "
|
| + "const ['one','two',])",
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should extract primitive literals', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @DummyAnnotation(true)
|
| + @DummyAnnotation(1.0)
|
| + @DummyAnnotation(1)
|
| + @DummyAnnotation(null)
|
| + class Engine {}
|
| +
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.DummyAnnotation(true)',
|
| + 'const import_1.DummyAnnotation(1.0)',
|
| + 'const import_1.DummyAnnotation(1)',
|
| + 'const import_1.DummyAnnotation(null)',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should extract formatter', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Formatter()
|
| + class Engine {}
|
| +
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Formatter()',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should skip and warn on unserializable annotations', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Decorator(module: MissingType.module)
|
| + class Car {
|
| + }
|
| +
|
| + main() {}
|
| + '''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Car': [
|
| + 'null',
|
| + ]
|
| + },
|
| + messages: [
|
| + // 'warning: Unable to serialize annotation @NgFoo. '
|
| + // '(web/main.dart 2 16)',
|
| + 'warning: Unable to serialize annotation '
|
| + '@Decorator(module: MissingType.module). '
|
| + '(web/main.dart 2 16)',
|
| + ]);
|
| + });
|
| +
|
| + it('should extract types across libs', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| + import 'package:a/b.dart';
|
| +
|
| + @Decorator(module: Car.module)
|
| + class Engine {
|
| + }
|
| +
|
| + main() {}
|
| + ''',
|
| + 'a|lib/b.dart': '''
|
| + class Car {
|
| + static module() => null;
|
| + }
|
| + ''',
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + 'import \'package:a/b.dart\' as import_2;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(module: import_2.Car.module)',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should not gather non-member annotations', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + class Engine {
|
| + Engine() {
|
| + @Decorator()
|
| + print('something');
|
| + }
|
| + }
|
| + main() {}
|
| + ''',
|
| + });
|
| + });
|
| +
|
| + it('properly escapes strings', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': r'''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @DummyAnnotation('foo\' \\')
|
| + class Engine {
|
| + }
|
| +
|
| + main() {}
|
| + ''',
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + r'''const import_1.DummyAnnotation('foo\' \\')''',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('maintains string formatting', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': r'''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @DummyAnnotation(r"""multiline
|
| + string""")
|
| + class Engine {
|
| + }
|
| +
|
| + main() {}
|
| + ''',
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + r'''const import_1.DummyAnnotation(r"""multiline
|
| + string""")''',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should reference static and global properties', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Decorator(visibility: Directive.CHILDREN_VISIBILITY)
|
| + @Decorator(visibility: CONST_VALUE)
|
| + class Engine {}
|
| +
|
| + const int CONST_VALUE = 2;
|
| +
|
| + main() {}
|
| + ''',
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(visibility: '
|
| + 'import_1.Directive.CHILDREN_VISIBILITY)',
|
| + 'const import_1.Decorator(visibility: import_0.CONST_VALUE)',
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should reference static methods', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @Decorator(module: Engine.module)
|
| + class Engine {
|
| + static module() => null;
|
| + }
|
| +
|
| + main() {}
|
| + ''',
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(module: import_0.Engine.module)'
|
| + ]
|
| + });
|
| + });
|
| +
|
| + it('should not extract private annotations', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @_Foo()
|
| + @_foo
|
| + class Engine {
|
| + }
|
| +
|
| + class _Foo {
|
| + const _Foo();
|
| + }
|
| + const _Foo _foo = const _Foo();
|
| +
|
| + main() {}
|
| + ''',
|
| + },
|
| + messages: [
|
| + 'warning: Annotation @_Foo() is not public. (web/main.dart 2 16)',
|
| + 'warning: Annotation @_foo is not public. (web/main.dart 2 16)',
|
| + ]);
|
| + });
|
| +
|
| + it('supports named constructors', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @NgFoo.bar()
|
| + @NgFoo._private()
|
| + class Engine {
|
| + }
|
| +
|
| + class NgFoo extends Directive {
|
| + const NgFoo.bar();
|
| + const NgFoo._private();
|
| + }
|
| +
|
| + main() {}
|
| + ''',
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + '''const import_0.NgFoo.bar()''',
|
| + ]
|
| + },
|
| + messages: [
|
| + 'warning: Annotation @NgFoo._private() is not public. '
|
| + '(web/main.dart 2 16)',
|
| + ]);
|
| + });
|
| +
|
| + it('skips non-Ng* annotations', () {
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': '''
|
| + import 'package:angular/angular.dart';
|
| +
|
| + @proxy
|
| + @Foo()
|
| + class Engine {}
|
| +
|
| + class Foo {}
|
| +
|
| + main() {}
|
| + ''',
|
| + },
|
| + imports: [],
|
| + classes: {});
|
| + });
|
| +
|
| + it('does not modify annotations in-place', () {
|
| + var main = '''
|
| + import 'package:angular/angular.dart';
|
| + import 'second.dart';
|
| +
|
| + @Decorator(map: {})
|
| + class Engine {
|
| + @NgTwoWay('two-way-stuff')
|
| + String get twoWayStuff => null;
|
| + }
|
| + main() {}
|
| + ''';
|
| + return generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': main,
|
| + 'a|web/second.dart': '''library second;'''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(map: const {'
|
| + '\'two-way-stuff\': \'<=>twoWayStuff\'})',
|
| + ]
|
| + }).then((_) => generates(phases,
|
| + inputs: {
|
| + 'angular|lib/angular.dart': libAngular,
|
| + 'a|web/main.dart': main,
|
| + 'a|web/second.dart': '''library a.second;'''
|
| + },
|
| + imports: [
|
| + 'import \'main.dart\' as import_0;',
|
| + 'import \'package:angular/angular.dart\' as import_1;',
|
| + ],
|
| + classes: {
|
| + 'import_0.Engine': [
|
| + 'const import_1.Decorator(map: const {'
|
| + '\'two-way-stuff\': \'<=>twoWayStuff\'})',
|
| + ]
|
| + }));
|
| + });
|
| + });
|
| +}
|
| +
|
| +Future generates(List<List<Transformer>> phases,
|
| + {Map<String, String> inputs, Iterable<String> imports: const [],
|
| + Map classes: const {},
|
| + Iterable<String> messages: const []}) {
|
| +
|
| + var buffer = new StringBuffer();
|
| + buffer.write('$header\n');
|
| + for (var i in imports) {
|
| + buffer.write('$i\n');
|
| + }
|
| + buffer.write('$boilerPlate\n');
|
| + for (var className in classes.keys) {
|
| + buffer.write(' $className: const [\n');
|
| + for (var annotation in classes[className]) {
|
| + buffer.write(' $annotation,\n');
|
| + }
|
| + buffer.write(' ],\n');
|
| + }
|
| +
|
| + buffer.write('$footer\n');
|
| +
|
| + return tests.applyTransformers(phases,
|
| + inputs: inputs,
|
| + results: {
|
| + 'a|web/main_static_metadata.dart': buffer.toString()
|
| + },
|
| + messages: messages);
|
| +}
|
| +
|
| +const String header = '''
|
| +library a.web.main.generated_metadata;
|
| +
|
| +import 'package:angular/core/registry.dart' show MetadataExtractor;
|
| +import 'package:di/di.dart' show Module;
|
| +''';
|
| +
|
| +const String boilerPlate = '''
|
| +Module get metadataModule => new Module()
|
| + ..value(MetadataExtractor, new _StaticMetadataExtractor());
|
| +
|
| +class _StaticMetadataExtractor implements MetadataExtractor {
|
| + Iterable call(Type type) {
|
| + var annotations = typeAnnotations[type];
|
| + if (annotations != null) {
|
| + return annotations;
|
| + }
|
| + return [];
|
| + }
|
| +}
|
| +
|
| +final Map<Type, Object> typeAnnotations = {''';
|
| +
|
| +const String footer = '''
|
| +};''';
|
| +
|
| +
|
| +const String libAngular = '''
|
| +library angular.core.annotation_src;
|
| +
|
| +class Formatter {};
|
| +
|
| +class Directive {
|
| + Directive({map: const {}});
|
| + static const int CHILDREN_VISIBILITY = 1;
|
| +}
|
| +
|
| +class Decorator extends Directive {
|
| + const Decorator({selector, module, map, visibility, exportExpressions}) :
|
| + super(map: map);
|
| +}
|
| +
|
| +class DummyAnnotation extends Directive {
|
| + const DummyAnnotation(object);
|
| +}
|
| +
|
| +class NgOneWay {
|
| + const NgOneWay(arg);
|
| +}
|
| +
|
| +class NgTwoWay {
|
| + const NgTwoWay(arg);
|
| +}
|
| +
|
| +class NgCallback {
|
| + const NgCallback(arg);
|
| +}
|
| +
|
| +class NgAttr {
|
| + const NgAttr(arg);
|
| +}
|
| +
|
| +class NgOneWayOneTime {
|
| + const NgOneWayOneTime(arg);
|
| +}
|
| +''';
|
|
|