X Tutup
Skip to content

Commit fd46b49

Browse files
committed
feat(transformers): directive aliases in Dart transformers (fix #1747)
1 parent 46502e4 commit fd46b49

File tree

43 files changed

+914
-291
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+914
-291
lines changed

modules/angular2/src/forms/form_builder.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {Injectable} from 'angular2/di';
12
import {StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
23
import {isPresent, isArray} from 'angular2/src/facade/lang';
34
import * as modelModule from './model';
@@ -67,6 +68,7 @@ import * as modelModule from './model';
6768
*
6869
* ```
6970
*/
71+
@Injectable()
7072
export class FormBuilder {
7173
group(controlsConfig: StringMap<string, any>,
7274
extra: StringMap<string, any> = null): modelModule.ControlGroup {

modules/angular2/src/transform/common/names.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ const REFLECTOR_VAR_NAME = 'reflector';
66
const TRANSFORM_DYNAMIC_MODE = 'transform_dynamic';
77
const DEPS_EXTENSION = '.ng_deps.dart';
88
const META_EXTENSION = '.ng_meta.json';
9+
// TODO(sigmund): consider merging into .ng_meta by generating local metadata
10+
// upfront (rather than extracting it from ng_deps).
11+
const ALIAS_EXTENSION = '.aliases.json';
912
const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities';
1013
const REGISTER_TYPE_METHOD_NAME = 'registerType';
1114
const REGISTER_GETTERS_METHOD_NAME = 'registerGetters';
@@ -20,6 +23,10 @@ String toMetaExtension(String uri) =>
2023
String toDepsExtension(String uri) =>
2124
_toExtension(uri, const [META_EXTENSION, '.dart'], DEPS_EXTENSION);
2225

26+
/// Returns `uri` with its extension updated to [ALIAS_EXTENSION].
27+
String toAliasExtension(String uri) =>
28+
_toExtension(uri, const [DEPS_EXTENSION, '.dart'], ALIAS_EXTENSION);
29+
2330
/// Returns `uri` with its extension updated to `toExtension` if its
2431
/// extension is currently in `fromExtension`.
2532
String _toExtension(
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
library angular2.transform.common.ng_meta;
2+
3+
import 'package:angular2/src/render/api.dart';
4+
import 'package:angular2/src/render/dom/convert.dart';
5+
import 'logging.dart';
6+
7+
/// Metadata about directives and directive aliases.
8+
///
9+
/// [NgMeta] is used in three stages of the transformation process. First we
10+
/// store directive aliases exported from a single file in an [NgMeta] instance.
11+
/// Later we use another [NgMeta] instance to store more information about a
12+
/// single file, including both directive aliases and directives extracted from
13+
/// the corresponding `.ng_deps.dart` file. Further down the compilation
14+
/// process, the template compiler needs to reason about the namespace of import
15+
/// prefixes, so it will combine multple [NgMeta] instances together if they
16+
/// were imported into a file with the same prefix.
17+
///
18+
/// Instances of this class are serialized into `.aliases.json` and
19+
/// `.ng_meta.json` files as intermediate assets to make the compilation process
20+
/// easier.
21+
class NgMeta {
22+
/// Directive metadata for each type annotated as a directive.
23+
final Map<String, DirectiveMetadata> types;
24+
25+
/// List of other types and names associated with a given name.
26+
final Map<String, List<String>> aliases;
27+
28+
NgMeta(this.types, this.aliases);
29+
30+
NgMeta.empty() : this({}, {});
31+
32+
bool get isEmpty => types.isEmpty && aliases.isEmpty;
33+
34+
/// Parse from the serialized form produced by [toJson].
35+
factory NgMeta.fromJson(Map json) {
36+
var types = {};
37+
var aliases = {};
38+
for (var key in json.keys) {
39+
var entry = json[key];
40+
if (entry['kind'] == 'type') {
41+
types[key] = directiveMetadataFromMap(entry['value']);
42+
} else if (entry['kind'] == 'alias') {
43+
aliases[key] = entry['value'];
44+
}
45+
}
46+
return new NgMeta(types, aliases);
47+
}
48+
49+
/// Serialized representation of this instance.
50+
Map toJson() {
51+
var result = {};
52+
types.forEach((k, v) {
53+
result[k] = {'kind': 'type', 'value': directiveMetadataToMap(v)};
54+
});
55+
56+
aliases.forEach((k, v) {
57+
result[k] = {'kind': 'alias', 'value': v};
58+
});
59+
return result;
60+
}
61+
62+
/// Merge into this instance all information from [other].
63+
void addAll(NgMeta other) {
64+
types.addAll(other.types);
65+
aliases.addAll(other.aliases);
66+
}
67+
68+
/// Returns the metadata for every type associated with the given [alias].
69+
List<DirectiveMetadata> flatten(String alias) {
70+
var result = [];
71+
var seen = new Set();
72+
helper(name) {
73+
if (!seen.add(name)) {
74+
logger.warning('Circular alias dependency for "$name".');
75+
return;
76+
}
77+
if (types.containsKey(name)) {
78+
result.add(types[name]);
79+
} else if (aliases.containsKey(name)) {
80+
aliases[name].forEach(helper);
81+
} else {
82+
logger.warning('Unknown alias: "$name".');
83+
}
84+
}
85+
helper(alias);
86+
return result;
87+
}
88+
}
Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,49 @@
11
library angular2.transform.directive_metadata_extractor.extractor;
22

33
import 'dart:async';
4+
import 'dart:convert';
45

56
import 'package:analyzer/analyzer.dart';
6-
import 'package:angular2/src/render/api.dart';
77
import 'package:angular2/src/transform/common/asset_reader.dart';
88
import 'package:angular2/src/transform/common/logging.dart';
99
import 'package:angular2/src/transform/common/names.dart';
1010
import 'package:angular2/src/transform/common/ng_deps.dart';
11+
import 'package:angular2/src/transform/common/ng_meta.dart';
1112
import 'package:barback/barback.dart';
1213
import 'package:code_transformers/assets.dart';
1314

14-
/// Returns a map from a class name (that is, its `Identifier` stringified)
15-
/// to [DirectiveMetadata] for all `Directive`-annotated classes visible
16-
/// in a file importing `entryPoint`. That is, this includes all
17-
/// `Directive` annotated classes in `entryPoint` and any assets which it
18-
/// `export`s.
19-
/// Returns `null` if there are no `Directive`-annotated classes in
20-
/// `entryPoint`.
21-
Future<Map<String, DirectiveMetadata>> extractDirectiveMetadata(
22-
AssetReader reader, AssetId entryPoint) async {
15+
/// Returns [NgMeta] associated with [entryPoint].
16+
///
17+
/// This includes entries for every `Directive`-annotated classes and
18+
/// constants that match the directive-aliases pattern, which are visible in a
19+
/// file importing `entryPoint`. That is, this includes all `Directive`
20+
/// annotated public classes in `entryPoint`, all `DirectiveAlias` annotated
21+
/// public variables, and any assets which it `export`s. Returns an empty
22+
/// [NgMeta] if there are no `Directive`-annotated classes in `entryPoint`.
23+
Future<NgMeta> extractDirectiveMetadata(
24+
AssetReader reader, AssetId entryPoint) {
2325
return _extractDirectiveMetadataRecursive(reader, entryPoint);
2426
}
2527

2628
var _nullFuture = new Future.value(null);
2729

28-
Future<Map<String, DirectiveMetadata>> _extractDirectiveMetadataRecursive(
30+
Future<NgMeta> _extractDirectiveMetadataRecursive(
2931
AssetReader reader, AssetId entryPoint) async {
30-
if (!(await reader.hasInput(entryPoint))) return null;
32+
var ngMeta = new NgMeta.empty();
33+
if (!(await reader.hasInput(entryPoint))) return ngMeta;
3134

3235
var ngDeps = await NgDeps.parse(reader, entryPoint);
33-
var baseMap = _metadataMapFromNgDeps(ngDeps);
36+
_extractMetadata(ngDeps, ngMeta);
37+
38+
var aliasesFile =
39+
new AssetId(entryPoint.package, toAliasExtension(entryPoint.path));
40+
41+
if (await reader.hasInput(aliasesFile)) {
42+
ngMeta.addAll(new NgMeta.fromJson(
43+
JSON.decode(await reader.readAsString(aliasesFile))));
44+
}
3445

35-
return Future.wait(ngDeps.exports.map((export) {
46+
await Future.wait(ngDeps.exports.map((export) {
3647
var uri = stringLiteralToString(export.uri);
3748
if (uri.startsWith('dart:')) return _nullFuture;
3849

@@ -41,25 +52,16 @@ Future<Map<String, DirectiveMetadata>> _extractDirectiveMetadataRecursive(
4152
errorOnAbsolute: false);
4253
if (assetId == entryPoint) return _nullFuture;
4354
return _extractDirectiveMetadataRecursive(reader, assetId)
44-
.then((exportMap) {
45-
if (exportMap != null) {
46-
if (baseMap == null) {
47-
baseMap = exportMap;
48-
} else {
49-
baseMap.addAll(exportMap);
50-
}
51-
}
52-
});
53-
})).then((_) => baseMap);
55+
.then(ngMeta.addAll);
56+
}));
57+
return ngMeta;
5458
}
5559

56-
Map<String, DirectiveMetadata> _metadataMapFromNgDeps(NgDeps ngDeps) {
57-
if (ngDeps == null || ngDeps.registeredTypes.isEmpty) return null;
58-
var retVal = <String, DirectiveMetadata>{};
59-
ngDeps.registeredTypes.forEach((rType) {
60-
if (rType.directiveMetadata != null) {
61-
retVal['${rType.typeName}'] = rType.directiveMetadata;
62-
}
60+
// TODO(sigmund): rather than having to parse it from generated code. we should
61+
// be able to produce directly all information we need for ngMeta.
62+
void _extractMetadata(NgDeps ngDeps, NgMeta ngMeta) {
63+
if (ngDeps == null) return;
64+
ngDeps.registeredTypes.forEach((type) {
65+
ngMeta.types[type.typeName.name] = type.directiveMetadata;
6366
});
64-
return retVal;
6567
}

modules/angular2/src/transform/directive_metadata_extractor/transformer.dart

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ library angular2.transform.directive_metadata_extractor.transformer;
33
import 'dart:async';
44
import 'dart:convert';
55

6-
import 'package:angular2/src/render/dom/convert.dart';
76
import 'package:angular2/src/transform/common/asset_reader.dart';
87
import 'package:angular2/src/transform/common/logging.dart' as log;
98
import 'package:angular2/src/transform/common/names.dart';
@@ -29,14 +28,10 @@ class DirectiveMetadataExtractor extends Transformer {
2928
var reader = new AssetReader.fromTransform(transform);
3029
var fromAssetId = transform.primaryInput.id;
3130

32-
var metadataMap = await extractDirectiveMetadata(reader, fromAssetId);
33-
if (metadataMap != null) {
34-
var jsonMap = <String, Map>{};
35-
metadataMap.forEach((k, v) {
36-
jsonMap[k] = directiveMetadataToMap(v);
37-
});
31+
var ngMeta = await extractDirectiveMetadata(reader, fromAssetId);
32+
if (ngMeta != null && !ngMeta.isEmpty) {
3833
transform.addOutput(new Asset.fromString(
39-
_outputAssetId(fromAssetId), _encoder.convert(jsonMap)));
34+
_outputAssetId(fromAssetId), _encoder.convert(ngMeta.toJson())));
4035
}
4136
}, errorMessage: 'Extracting ng metadata failed.');
4237
}

modules/angular2/src/transform/directive_processor/rewriter.dart

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:angular2/src/transform/common/interface_matcher.dart';
1111
import 'package:angular2/src/transform/common/logging.dart';
1212
import 'package:angular2/src/transform/common/names.dart';
1313
import 'package:angular2/src/transform/common/xhr_impl.dart';
14+
import 'package:angular2/src/transform/common/ng_meta.dart';
1415
import 'package:barback/barback.dart' show AssetId;
1516
import 'package:path/path.dart' as path;
1617

@@ -23,14 +24,19 @@ import 'visitors.dart';
2324
/// If no Angular 2 `Directive`s are found in `code`, returns the empty
2425
/// string unless `forceGenerate` is true, in which case an empty ngDeps
2526
/// file is created.
26-
Future<String> createNgDeps(
27-
AssetReader reader, AssetId assetId, AnnotationMatcher annotationMatcher,
27+
Future<String> createNgDeps(AssetReader reader, AssetId assetId,
28+
AnnotationMatcher annotationMatcher, NgMeta ngMeta,
2829
{bool inlineViews}) async {
2930
// TODO(kegluneq): Shortcut if we can determine that there are no
3031
// [Directive]s present, taking into account `export`s.
3132
var writer = new AsyncStringWriter();
32-
var visitor = new CreateNgDepsVisitor(writer, assetId,
33-
new XhrImpl(reader, assetId), annotationMatcher, _interfaceMatcher,
33+
var visitor = new CreateNgDepsVisitor(
34+
writer,
35+
assetId,
36+
new XhrImpl(reader, assetId),
37+
annotationMatcher,
38+
_interfaceMatcher,
39+
ngMeta,
3440
inlineViews: inlineViews);
3541
var code = await reader.readAsString(assetId);
3642
parseCompilationUnit(code, name: assetId.path).accept(visitor);
@@ -49,10 +55,19 @@ InterfaceMatcher _interfaceMatcher = new InterfaceMatcher();
4955
/// associated .ng_deps.dart file.
5056
class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
5157
final AsyncStringWriter writer;
58+
59+
/// Output ngMeta information about aliases.
60+
// TODO(sigmund): add more to ngMeta. Currently this only contains aliasing
61+
// information, but we could produce here all the metadata we need and avoid
62+
// parsing the ngdeps files later.
63+
final NgMeta ngMeta;
64+
5265
/// Whether an Angular 2 `Injectable` has been found.
5366
bool _foundNgInjectable = false;
67+
5468
/// Whether this library `imports` or `exports` any non-'dart:' libraries.
5569
bool _usesNonLangLibs = false;
70+
5671
/// Whether we have written an import of base file
5772
/// (the file we are processing).
5873
bool _wroteBaseLibImport = false;
@@ -66,8 +81,13 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
6681
/// The assetId for the file which we are parsing.
6782
final AssetId assetId;
6883

69-
CreateNgDepsVisitor(AsyncStringWriter writer, AssetId assetId, XHR xhr,
70-
AnnotationMatcher annotationMatcher, InterfaceMatcher interfaceMatcher,
84+
CreateNgDepsVisitor(
85+
AsyncStringWriter writer,
86+
AssetId assetId,
87+
XHR xhr,
88+
AnnotationMatcher annotationMatcher,
89+
InterfaceMatcher interfaceMatcher,
90+
this.ngMeta,
7191
{bool inlineViews})
7292
: writer = writer,
7393
_copyVisitor = new ToSourceVisitor(writer),
@@ -223,6 +243,29 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
223243
return null;
224244
}
225245

246+
@override
247+
Object visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
248+
// We process any top-level declaration that fits the directive-alias
249+
// declaration pattern. Ideally we would use an annotation on the field to
250+
// help us filter out only what's needed, but unfortunately TypeScript
251+
// doesn't support decorators on variable declarations (see
252+
// angular/angular#1747 and angular/ts2dart#249 for context).
253+
outer: for (var variable in node.variables.variables) {
254+
var initializer = variable.initializer;
255+
if (initializer != null && initializer is ListLiteral) {
256+
var otherNames = [];
257+
for (var exp in initializer.elements) {
258+
// Only simple identifiers are supported for now.
259+
// TODO(sigmund): add support for prefixes (see issue #3232).
260+
if (exp is! SimpleIdentifier) continue outer;
261+
otherNames.add(exp.name);
262+
}
263+
ngMeta.aliases[variable.name.name] = otherNames;
264+
}
265+
}
266+
return null;
267+
}
268+
226269
Object _nodeToSource(AstNode node) {
227270
if (node == null) return null;
228271
return node.accept(_copyVisitor);

modules/angular2/src/transform/directive_processor/transformer.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
library angular2.transform.directive_processor.transformer;
22

33
import 'dart:async';
4+
import 'dart:convert';
45

56
import 'package:angular2/src/transform/common/asset_reader.dart';
67
import 'package:angular2/src/transform/common/logging.dart' as log;
78
import 'package:angular2/src/transform/common/names.dart';
89
import 'package:angular2/src/transform/common/options.dart';
10+
import 'package:angular2/src/transform/common/ng_meta.dart';
911
import 'package:barback/barback.dart';
1012

1113
import 'rewriter.dart';
@@ -32,8 +34,9 @@ class DirectiveProcessor extends Transformer {
3234
await log.initZoned(transform, () async {
3335
var asset = transform.primaryInput;
3436
var reader = new AssetReader.fromTransform(transform);
37+
var ngMeta = new NgMeta.empty();
3538
var ngDepsSrc = await createNgDeps(
36-
reader, asset.id, options.annotationMatcher,
39+
reader, asset.id, options.annotationMatcher, ngMeta,
3740
inlineViews: options.inlineViews);
3841
if (ngDepsSrc != null && ngDepsSrc.isNotEmpty) {
3942
var ngDepsAssetId =
@@ -44,6 +47,12 @@ class DirectiveProcessor extends Transformer {
4447
}
4548
transform.addOutput(new Asset.fromString(ngDepsAssetId, ngDepsSrc));
4649
}
50+
if (!ngMeta.isEmpty) {
51+
var ngAliasesId =
52+
transform.primaryInput.id.changeExtension(ALIAS_EXTENSION);
53+
transform.addOutput(new Asset.fromString(ngAliasesId,
54+
new JsonEncoder.withIndent(" ").convert(ngMeta.toJson())));
55+
}
4756
}, errorMessage: 'Processing ng directives failed.');
4857
}
4958
}

0 commit comments

Comments
 (0)
X Tutup