Skip to content

Commit d0aceef

Browse files
author
Tim Blasi
committed
perf(dart/transform) Restructure transform to independent phases
Update summary: - Removes the need for resolution, gaining transform speed at the cost of some precision and ability to detect errors - Generates type registrations in the package alongside their declarations - Ensures that line numbers do not change in transformed user code
1 parent 08bd3a4 commit d0aceef

Some content is hidden

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

52 files changed

+1530
-318
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
library angular2.src.transform.bind_generator.generator;
2+
3+
import 'package:analyzer/analyzer.dart';
4+
import 'package:analyzer/src/generated/java_core.dart';
5+
import 'package:angular2/src/transform/common/logging.dart';
6+
import 'package:angular2/src/transform/common/names.dart';
7+
import 'package:angular2/src/transform/common/visitor_mixin.dart';
8+
9+
String createNgSetters(String code, String path) {
10+
if (_noSettersPresent(code)) return code;
11+
12+
var writer = new PrintStringWriter();
13+
parseCompilationUnit(
14+
code, name: path).accept(new CreateNgSettersVisitor(writer));
15+
return writer.toString();
16+
}
17+
18+
bool _noSettersPresent(String code) => code.indexOf('bind') < 0;
19+
20+
class CreateNgSettersVisitor extends ToSourceVisitor with VisitorMixin {
21+
final PrintWriter writer;
22+
final _ExtractSettersVisitor extractVisitor = new _ExtractSettersVisitor();
23+
24+
CreateNgSettersVisitor(PrintWriter writer)
25+
: this.writer = writer,
26+
super(writer);
27+
28+
@override
29+
Object visitMethodInvocation(MethodInvocation node) {
30+
var isRegisterType = node.methodName.toString() == REGISTER_TYPE_METHOD_NAME;
31+
// The first argument to a `registerType` call is the type.
32+
extractVisitor.currentName = node.argumentList.arguments[0] is Identifier
33+
? node.argumentList.arguments[0]
34+
: null;
35+
extractVisitor.bindPieces.clear();
36+
37+
var retVal = super.visitMethodInvocation(node);
38+
if (isRegisterType) {
39+
if (extractVisitor.bindPieces.isNotEmpty) {
40+
writer.print('..${REGISTER_SETTERS_METHOD_NAME}({');
41+
writer.print(extractVisitor.bindPieces.join(','));
42+
writer.print('})');
43+
}
44+
}
45+
return retVal;
46+
}
47+
48+
@override
49+
Object visitMapLiteralEntry(MapLiteralEntry node) {
50+
if (node.key is StringLiteral &&
51+
stringLiteralToString(node.key) == 'annotations') {
52+
node.value.accept(extractVisitor);
53+
}
54+
return super.visitMapLiteralEntry(node);
55+
}
56+
}
57+
58+
/// Visitor responsible for crawling the "annotations" value in a
59+
/// `registerType` call and generating setters from any "bind" values found.
60+
class _ExtractSettersVisitor extends Object
61+
with RecursiveAstVisitor<Object>, VisitorMixin {
62+
final List<String> bindPieces = [];
63+
Identifier currentName = null;
64+
65+
void _extractFromMapLiteral(MapLiteral map) {
66+
if (currentName == null) {
67+
logger.error('Unexpected code path: `currentName` should never be null');
68+
}
69+
map.entries.forEach((entry) {
70+
// TODO(kegluneq): Remove this restriction
71+
if (entry.key is SimpleStringLiteral) {
72+
var propName = entry.key.value;
73+
bindPieces.add('"${propName}": ('
74+
'${currentName} o, String value) => o.${propName} = value');
75+
} else {
76+
logger.error('`bind` currently only supports string literals');
77+
}
78+
});
79+
}
80+
81+
@override
82+
Object visitNamedExpression(NamedExpression node) {
83+
if (node.name.label.toString() == 'bind') {
84+
// TODO(kegluneq): Remove this restriction.
85+
if (node.expression is MapLiteral) {
86+
_extractFromMapLiteral(node.expression);
87+
}
88+
return null;
89+
}
90+
return super.visitNamedExpression(node);
91+
}
92+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
library angular2.src.transform.bind_generator.transformer;
2+
3+
import 'dart:async';
4+
import 'package:angular2/src/transform/common/formatter.dart';
5+
import 'package:angular2/src/transform/common/logging.dart' as log;
6+
import 'package:angular2/src/transform/common/names.dart';
7+
import 'package:angular2/src/transform/common/options.dart';
8+
import 'package:barback/barback.dart';
9+
10+
import 'generator.dart';
11+
12+
/// Transformer responsible for reading .ngDeps.dart files and generating
13+
/// setters from the "annotations" information in the generated
14+
/// `registerType` calls.
15+
///
16+
/// These setters are registered in the same `setupReflection` function with
17+
/// the `registerType` calls.
18+
class BindGenerator extends Transformer {
19+
final TransformerOptions options;
20+
21+
BindGenerator(this.options);
22+
23+
@override
24+
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
25+
26+
@override
27+
Future apply(Transform transform) async {
28+
log.init(transform);
29+
30+
try {
31+
var assetCode = await transform.primaryInput.readAsString();
32+
var assetPath = transform.primaryInput.id.path;
33+
var transformedCode = createNgSetters(assetCode, assetPath);
34+
transform.addOutput(new Asset.fromString(transform.primaryInput.id,
35+
formatter.format(transformedCode, uri: assetPath)));
36+
} catch (ex, stackTrace) {
37+
log.logger.error('Creating ng setters failed.\n'
38+
'Exception: $ex\n'
39+
'Stack Trace: $stackTrace');
40+
}
41+
}
42+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
library angular2.src.transform.common.formatter;
2+
3+
import 'package:dart_style/dart_style.dart';
4+
5+
import 'logging.dart';
6+
7+
DartFormatter _formatter;
8+
9+
void init(DartFormatter formatter) {
10+
if (_formatter != null) {
11+
logger.warning('Formatter is being overwritten.');
12+
}
13+
_formatter = formatter;
14+
}
15+
16+
DartFormatter get formatter {
17+
if (_formatter == null) {
18+
logger.info('Formatter never initialized, using default formatter.');
19+
_formatter = new DartFormatter();
20+
}
21+
return _formatter;
22+
}

modules/angular2/src/transform/logging.dart renamed to modules/angular2/src/transform/common/logging.dart

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
library angular2.src.transform.logging;
1+
library angular2.src.transform.common.logging;
22

33
import 'package:barback/barback.dart';
44
import 'package:code_transformers/messages/build_logger.dart';
@@ -7,12 +7,7 @@ BuildLogger _logger;
77

88
/// Prepares [logger] for use throughout the transformer.
99
void init(Transform t) {
10-
if (_logger == null) {
11-
_logger = new BuildLogger(t);
12-
} else {
13-
_logger.fine('Attempted to initialize logger multiple times.',
14-
asset: t.primaryInput.id);
15-
}
10+
_logger = new BuildLogger(t);
1611
}
1712

1813
/// The logger the transformer should use for messaging.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
library angular2.src.transform.common.names;
2+
3+
const SETUP_METHOD_NAME = 'setupReflection';
4+
const REFLECTOR_VAR_NAME = 'reflector';
5+
const DEPS_EXTENSION = '.ngDeps.dart';
6+
const REGISTER_TYPE_METHOD_NAME = 'registerType';
7+
const REGISTER_SETTERS_METHOD_NAME = 'registerSetters';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
library angular2.src.transform.common.ng_data;
2+
3+
import 'dart:convert';
4+
5+
const NG_DATA_VERSION = 1;
6+
7+
class NgData extends Object {
8+
int importOffset = 0;
9+
int registerOffset = 0;
10+
List<String> imports = [];
11+
12+
NgData();
13+
14+
factory NgData.fromJson(String json) {
15+
var data = JSON.decode(json);
16+
return new NgData()
17+
..importOffset = data['importOffset']
18+
..registerOffset = data['registerOffset']
19+
..imports = data['imports'];
20+
}
21+
22+
String toJson() {
23+
return JSON.encode({
24+
'version': NG_DATA_VERSION,
25+
'importOffset': importOffset,
26+
'registerOffset': registerOffset,
27+
'imports': imports
28+
});
29+
}
30+
31+
@override
32+
String toString() {
33+
return '[NgData: ${toJson()}]';
34+
}
35+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library angular2.src.transform.common.options;
2+
3+
const ENTRY_POINT_PARAM = 'entry_point';
4+
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_point';
5+
6+
/// Provides information necessary to transform an Angular2 app.
7+
class TransformerOptions {
8+
/// The path to the file where the application's call to [bootstrap] is.
9+
// TODO(kegluneq): Allow multiple entry points.
10+
final String entryPoint;
11+
12+
/// The reflection entry point, that is, the path to the file where the
13+
/// application's [ReflectionCapabilities] are set.
14+
final String reflectionEntryPoint;
15+
16+
TransformerOptions._internal(this.entryPoint, this.reflectionEntryPoint);
17+
18+
factory TransformerOptions(String entryPoint, {String reflectionEntryPoint}) {
19+
if (entryPoint == null) {
20+
throw new ArgumentError.notNull(ENTRY_POINT_PARAM);
21+
} else if (entryPoint.isEmpty) {
22+
throw new ArgumentError.value(entryPoint, 'entryPoint');
23+
}
24+
if (reflectionEntryPoint == null || entryPoint.isEmpty) {
25+
reflectionEntryPoint = entryPoint;
26+
}
27+
return new TransformerOptions._internal(entryPoint, reflectionEntryPoint);
28+
}
29+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
library angular2.src.transform.common;
2+
3+
import 'package:analyzer/analyzer.dart';
4+
import 'package:analyzer/src/generated/java_core.dart';
5+
import 'package:analyzer/src/generated/scanner.dart';
6+
7+
/// Visitor providing common methods for concrete implementations.
8+
class VisitorMixin {
9+
PrintWriter writer;
10+
11+
/**
12+
* Visit the given function body, printing the prefix before if given body is not empty.
13+
*
14+
* @param prefix the prefix to be printed if there is a node to visit
15+
* @param body the function body to be visited
16+
*/
17+
void visitFunctionWithPrefix(String prefix, FunctionBody body) {
18+
if (body is! EmptyFunctionBody) {
19+
writer.print(prefix);
20+
}
21+
visitNode(body);
22+
}
23+
24+
/// Safely visit [node].
25+
void visitNode(AstNode node) {
26+
if (node != null) {
27+
node.accept(this);
28+
}
29+
}
30+
31+
/// Print a list of [nodes] without any separation.
32+
void visitNodeList(NodeList<AstNode> nodes) {
33+
visitNodeListWithSeparator(nodes, "");
34+
}
35+
36+
/// Print a list of [nodes], separated by the given [separator].
37+
void visitNodeListWithSeparator(NodeList<AstNode> nodes, String separator) {
38+
if (nodes != null) {
39+
int size = nodes.length;
40+
for (int i = 0; i < size; i++) {
41+
if (i > 0) {
42+
writer.print(separator);
43+
}
44+
nodes[i].accept(this);
45+
}
46+
}
47+
}
48+
49+
/// Print a list of [nodes], separated by the given [separator] and
50+
/// preceded by the given [prefix].
51+
void visitNodeListWithSeparatorAndPrefix(
52+
String prefix, NodeList<AstNode> nodes, String separator) {
53+
if (nodes != null) {
54+
int size = nodes.length;
55+
if (size > 0) {
56+
writer.print(prefix);
57+
for (int i = 0; i < size; i++) {
58+
if (i > 0) {
59+
writer.print(separator);
60+
}
61+
nodes[i].accept(this);
62+
}
63+
}
64+
}
65+
}
66+
67+
/// Print a list of [nodes], separated by the given [separator] and
68+
/// succeeded by the given [suffix].
69+
void visitNodeListWithSeparatorAndSuffix(
70+
NodeList<AstNode> nodes, String separator, String suffix) {
71+
if (nodes != null) {
72+
int size = nodes.length;
73+
if (size > 0) {
74+
for (int i = 0; i < size; i++) {
75+
if (i > 0) {
76+
writer.print(separator);
77+
}
78+
nodes[i].accept(this);
79+
}
80+
writer.print(suffix);
81+
}
82+
}
83+
}
84+
85+
/// If [node] is null does nothing. Otherwise, prints [prefix], then
86+
/// visits [node].
87+
void visitNodeWithPrefix(String prefix, AstNode node) {
88+
if (node != null) {
89+
writer.print(prefix);
90+
node.accept(this);
91+
}
92+
}
93+
94+
/// If [node] is null does nothing. Otherwise, visits [node], then prints
95+
/// [suffix].
96+
void visitNodeWithSuffix(AstNode node, String suffix) {
97+
if (node != null) {
98+
node.accept(this);
99+
writer.print(suffix);
100+
}
101+
}
102+
103+
/// Safely visit [node], printing [suffix] after the node if it is
104+
/// non-`null`.
105+
void visitTokenWithSuffix(Token token, String suffix) {
106+
if (token != null) {
107+
writer.print(token.lexeme);
108+
writer.print(suffix);
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)