feat(dart/transform): Add debug transform parameters

Add two transform parameters to aid in debugging the transformer
- `mirror_mode`, with values {`debug`, `none`, and `verbose`}
- `init_reflector`, with values {`true`, `false`}

`mirror_mode`:
- `debug`: Allow reflective access, but log a message if it is used
- `none`: Remove reflective access, `throw` if it is used. Default value
- `verbose`: Allow reflective access, log a stack trace if it is used

`init_reflector`: Whether to generate calls to our generated
`initReflector` code.

These will be useful to reveal areas where the transformer is not generating
appropriate code and to quickly see where reflective accesses occur.

When the pub mode is `transform_dynamic`, we run in MirrorMode.debug
with `init_reflector = false`. This is used for testing purposes.
This commit is contained in:
Tim Blasi 2015-04-17 11:12:35 -07:00
parent 5b4eb0c6d7
commit 77b31ab42f
22 changed files with 343 additions and 22 deletions

View File

@ -14,6 +14,7 @@ dependencies:
code_transformers: '^0.2.5'
dart_style: '^0.1.3'
html: '^0.12.0'
logging: '>=0.9.0 <0.11.0'
source_span: '^1.0.0'
stack_trace: '^1.1.1'
transformers:

View File

@ -0,0 +1,55 @@
library reflection.debug_reflection_capabilities;
import 'package:logging/logging.dart';
import 'package:stack_trace/stack_trace.dart';
import 'types.dart';
import 'reflection_capabilities.dart' as standard;
class ReflectionCapabilities extends standard.ReflectionCapabilities {
final bool _verbose;
final Logger _log = new Logger('ReflectionCapabilities');
ReflectionCapabilities({bool verbose: false})
: _verbose = verbose,
super() {
Logger.root.level = _verbose ? Level.ALL : Level.INFO;
Logger.root.onRecord.listen((LogRecord rec) {
print('[${rec.loggerName}(${rec.level.name})]: ${rec.message}');
});
}
void _notify(String methodName, param) {
var trace = _verbose ? ' ${Trace.format(new Trace.current())}' : '';
_log.info('"$methodName" requested for "$param".$trace');
}
Function factory(Type type) {
_notify('factory', type);
return super.factory(type);
}
List<List> parameters(typeOrFunc) {
_notify('parameters', typeOrFunc);
return super.parameters(typeOrFunc);
}
List annotations(typeOrFunc) {
_notify('annotations', typeOrFunc);
return super.annotations(typeOrFunc);
}
GetterFn getter(String name) {
_notify('getter', name);
return super.getter(name);
}
SetterFn setter(String name) {
_notify('setter', name);
return super.setter(name);
}
MethodFn method(String name) {
_notify('method', name);
return super.method(name);
}
}

View File

@ -0,0 +1,10 @@
library angular2.transform.common.mirror_mode;
/// Modes for mirror use.
/// `none` is the default value and signifies that mirror use should be
/// removed.
/// `debug` allows the use of mirrors and logs a notice whenever they are
/// accessed.
/// `verbose` allows the use of mirrors and logs a stack trace whenever they
/// are accessed.
enum MirrorMode { debug, none, verbose }

View File

@ -2,10 +2,10 @@ library angular2.transform.common.names;
const SETUP_METHOD_NAME = 'initReflector';
const REFLECTOR_VAR_NAME = 'reflector';
const TRANSFORM_DYNAMIC_MODE = 'transform_dynamic';
const DEPS_EXTENSION = '.ng_deps.dart';
const REFLECTION_CAPABILITIES_NAME = 'ReflectionCapabilities';
const REGISTER_TYPE_METHOD_NAME = 'registerType';
const REGISTER_GETTERS_METHOD_NAME = 'registerGetters';
const REGISTER_SETTERS_METHOD_NAME = 'registerSetters';
const REGISTER_METHODS_METHOD_NAME = 'registerMethods';
const TRANSFORM_MODE = 'ngstatic';

View File

@ -1,5 +1,7 @@
library angular2.transform.common.options;
import 'mirror_mode.dart';
const ENTRY_POINT_PARAM = 'entry_points';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
@ -15,15 +17,22 @@ class TransformerOptions {
/// The `BarbackMode#name` we are running in.
final String modeName;
TransformerOptions._internal(
this.entryPoints, this.reflectionEntryPoints, this.modeName);
/// The [MirrorMode] to use for the transformation.
final MirrorMode mirrorMode;
/// Whether to generate calls to our generated `initReflector` code
final bool initReflector;
TransformerOptions._internal(this.entryPoints, this.reflectionEntryPoints,
this.modeName, this.mirrorMode, this.initReflector);
factory TransformerOptions(List<String> entryPoints,
{List<String> reflectionEntryPoints, String modeName: 'release'}) {
{List<String> reflectionEntryPoints, String modeName: 'release',
MirrorMode mirrorMode: MirrorMode.none, bool initReflector: true}) {
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
reflectionEntryPoints = entryPoints;
}
return new TransformerOptions._internal(
entryPoints, reflectionEntryPoints, modeName);
return new TransformerOptions._internal(entryPoints, reflectionEntryPoints,
modeName, mirrorMode, initReflector);
}
}

View File

@ -1,6 +1,7 @@
library angular2.transform.common.options_reader;
import 'package:barback/barback.dart';
import 'mirror_mode.dart';
import 'options.dart';
TransformerOptions parseBarbackSettings(BarbackSettings settings) {
@ -8,9 +9,27 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
var entryPoints = _readFileList(config, ENTRY_POINT_PARAM);
var reflectionEntryPoints =
_readFileList(config, REFLECTION_ENTRY_POINT_PARAM);
var initReflector = !config.containsKey('init_reflector') ||
config['init_reflector'] != false;
String mirrorModeVal =
config.containsKey('mirror_mode') ? config['mirror_mode'] : '';
var mirrorMode = MirrorMode.none;
switch (mirrorModeVal) {
case 'debug':
mirrorMode = MirrorMode.debug;
break;
case 'verbose':
mirrorMode = MirrorMode.verbose;
break;
default:
mirrorMode = MirrorMode.none;
break;
}
return new TransformerOptions(entryPoints,
reflectionEntryPoints: reflectionEntryPoints,
modeName: settings.mode.name);
modeName: settings.mode.name,
mirrorMode: mirrorMode,
initReflector: initReflector);
}
/// Cribbed from the polymer project.

View File

@ -4,6 +4,7 @@ import 'dart:async';
import 'package:analyzer/analyzer.dart';
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/mirror_mode.dart';
import 'codegen.dart';
import 'rewriter.dart';
@ -15,12 +16,15 @@ import 'rewriter.dart';
/// This only searches the code in `reflectionEntryPoint`, not `part`s,
/// `import`s, `export`s, etc.
Future<String> removeReflectionCapabilities(AssetReader reader,
AssetId reflectionEntryPoint, Iterable<AssetId> newEntryPoints) async {
AssetId reflectionEntryPoint, Iterable<AssetId> newEntryPoints,
{MirrorMode mirrorMode: MirrorMode.none,
bool writeStaticInit: true}) async {
var code = await reader.readAsString(reflectionEntryPoint);
var reflectionEntryPointPath = reflectionEntryPoint.path;
var newEntryPointPaths = newEntryPoints.map((id) => id.path);
var codegen = new Codegen(reflectionEntryPointPath, newEntryPointPaths);
return new Rewriter(code, codegen)
return new Rewriter(code, codegen,
mirrorMode: mirrorMode, writeStaticInit: writeStaticInit)
.rewrite(parseCompilationUnit(code, name: reflectionEntryPointPath));
}

View File

@ -2,7 +2,9 @@ library angular2.transform.reflection_remover.rewriter;
import 'package:analyzer/src/generated/ast.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/mirror_mode.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:path/path.dart' as path;
import 'ast_tester.dart';
import 'codegen.dart';
@ -11,9 +13,14 @@ class Rewriter {
final String _code;
final Codegen _codegen;
final AstTester _tester;
final MirrorMode _mirrorMode;
final bool _writeStaticInit;
Rewriter(this._code, this._codegen, {AstTester tester})
: _tester = tester == null ? const AstTester() : tester;
Rewriter(this._code, this._codegen, {AstTester tester,
MirrorMode mirrorMode: MirrorMode.none, bool writeStaticInit: true})
: _mirrorMode = mirrorMode,
_writeStaticInit = writeStaticInit,
_tester = tester == null ? const AstTester() : tester;
/// Rewrites the provided code removing imports of the
/// {@link ReflectionCapabilities} library and instantiations of
@ -51,8 +58,12 @@ class Rewriter {
'Found import prefix "${_codegen.prefix}" in source file.'
' Transform may not succeed.');
}
buf.write(_commentedNode(node));
if (!importAdded) {
if (_mirrorMode != MirrorMode.none) {
buf.write(_importDebugReflectionCapabilities(node));
} else {
buf.write(_commentedNode(node));
}
if (!importAdded && _writeStaticInit) {
buf.write(_codegen.codegenImport());
importAdded = true;
}
@ -67,8 +78,19 @@ class Rewriter {
node = node.parent;
}
buf.write(_code.substring(lastIdx, node.offset));
buf.write(_commentedNode(node));
if (!setupAdded) {
switch (_mirrorMode) {
case MirrorMode.debug:
buf.write(node);
break;
case MirrorMode.verbose:
buf.write(_instantiateVerboseReflectionCapabilities(assignNode));
break;
case MirrorMode.none:
default:
buf.write(_commentedNode(node));
break;
}
if (!setupAdded && _writeStaticInit) {
buf.write(_codegen.codegenSetupReflectionCall(
reflectorAssignment: assignNode));
setupAdded = true;
@ -79,8 +101,24 @@ class Rewriter {
return buf.toString();
}
String _instantiateVerboseReflectionCapabilities(
AssignmentExpression assignNode) {
if (assignNode.rightHandSide is! InstanceCreationExpression) {
return '$assignNode;';
}
var rhs = (assignNode.rightHandSide as InstanceCreationExpression);
return '${assignNode.leftHandSide} ${assignNode.operator} '
'new ${rhs.constructorName}(verbose: true);';
}
String _importDebugReflectionCapabilities(ImportDirective node) {
var uri = '${node.uri}';
uri = path.join(path.dirname(uri), 'debug_${path.basename(uri)}');
var asClause = node.prefix != null ? ' as ${node.prefix}' : '';
return 'import $uri$asClause;';
}
String _commentedNode(AstNode node) {
// TODO(kegluneq): Return commented code once we generate all needed code.
return '/*${_code.substring(node.offset, node.end)}*/';
}
}

View File

@ -3,6 +3,7 @@ library angular2.transform.reflection_remover.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/mirror_mode.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:barback/barback.dart';
@ -38,8 +39,19 @@ class ReflectionRemover extends Transformer {
});
var reader = new AssetReader.fromTransform(transform);
var mirrorMode = options.mirrorMode;
var writeStaticInit = options.initReflector;
if (options.modeName == TRANSFORM_DYNAMIC_MODE) {
mirrorMode = MirrorMode.debug;
writeStaticInit = false;
log.logger.info('Running in "${options.modeName}", '
'mirrorMode: ${mirrorMode}, '
'writeStaticInit: ${writeStaticInit}.');
}
var transformedCode = await removeReflectionCapabilities(
reader, transform.primaryInput.id, newEntryPoints);
reader, transform.primaryInput.id, newEntryPoints,
mirrorMode: mirrorMode, writeStaticInit: writeStaticInit);
transform.addOutput(
new Asset.fromString(transform.primaryInput.id, transformedCode));
} catch (ex, stackTrace) {

View File

@ -14,8 +14,7 @@ main() {
var formatter = new DartFormatter();
var transform = new AngularTransformerGroup(new TransformerOptions(
['web/index.dart'],
reflectionEntryPoints: ['web/index.dart'], modeName: TRANSFORM_MODE));
['web/index.dart'], reflectionEntryPoints: ['web/index.dart']));
class IntegrationTestConfig {
final String name;

View File

@ -1,21 +1,46 @@
library angular2.test.transform.reflection_remover;
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/mirror_mode.dart';
import 'package:angular2/src/transform/reflection_remover/codegen.dart';
import 'package:angular2/src/transform/reflection_remover/rewriter.dart';
import 'package:guinness/guinness.dart';
import 'reflection_remover_files/expected/index.dart' as expected;
import 'debug_mirrors_files/expected/index.dart' as debug_mirrors;
import 'log_mirrors_files/expected/index.dart' as log_mirrors;
import 'verbose_files/expected/index.dart' as verbose_mirrors;
import '../common/read_file.dart';
void allTests() {
var codegen = new Codegen('web/index.dart', ['web/index.ng_deps.dart']);
var code = readFile('reflection_remover/index.dart');
it('should remove uses of mirrors & insert calls to generated code.', () {
var code =
readFile('reflection_remover/reflection_remover_files/index.dart');
it('should remove uses of mirrors & '
'insert calls to generated code by default.', () {
var output =
new Rewriter(code, codegen).rewrite(parseCompilationUnit(code));
expect(output).toEqual(expected.code);
});
it('should replace uses of mirrors with the debug implementation & '
'insert calls to generated code in `MirrorMode.debug`.', () {
var output = new Rewriter(code, codegen, mirrorMode: MirrorMode.debug)
.rewrite(parseCompilationUnit(code));
expect(output).toEqual(debug_mirrors.code);
});
it('should replace uses of mirrors with the verbose implementation '
'in `MirrorMode.verbose`.', () {
var output = new Rewriter(code, codegen, mirrorMode: MirrorMode.verbose)
.rewrite(parseCompilationUnit(code));
expect(output).toEqual(verbose_mirrors.code);
});
it('should not initialize the reflector when `writeStaticInit` is `false`.',
() {
var output = new Rewriter(code, codegen, writeStaticInit: false)
.rewrite(parseCompilationUnit(code));
expect(output).toEqual(log_mirrors.code);
});
}

View File

@ -0,0 +1,7 @@
Tests that the reflection removal step:
1. Comments out the import of reflection_capabilities.dart
2. Comments out the instantiation of `ReflectionCapabilities`
3. Adds the appropriate import.
4. Adds the call to `initReflector`
5. Does not change line numbers in the source.
6. Makes minimal changes to source offsets.

View File

@ -0,0 +1,22 @@
library angular2.test.transform.debug_reflection_remover_files;
// This file is intentionally formatted as a string to avoid having the
// automatic transformer prettify it.
//
// This file represents transformed user code. Because this code will be
// linked to output by a source map, we cannot change line numbers from the
// original code and we therefore add our generated code on the same line as
// those we are removing.
var code = """
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/debug_reflection_capabilities.dart';import 'index.ng_deps.dart' as ngStaticInit0;
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();ngStaticInit0.initReflector(reflector);
bootstrap(MyComponent);
}
""";

View File

@ -0,0 +1,10 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(MyComponent);
}

View File

@ -0,0 +1,7 @@
Tests that the reflection removal step:
1. Comments out the import of reflection_capabilities.dart
2. Comments out the instantiation of `ReflectionCapabilities`
3. Adds the appropriate import.
4. Adds the call to `initReflector`
5. Does not change line numbers in the source.
6. Makes minimal changes to source offsets.

View File

@ -0,0 +1,22 @@
library angular2.test.transform.debug_reflection_remover_files;
// This file is intentionally formatted as a string to avoid having the
// automatic transformer prettify it.
//
// This file represents transformed user code. Because this code will be
// linked to output by a source map, we cannot change line numbers from the
// original code and we therefore add our generated code on the same line as
// those we are removing.
var code = """
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/debug_reflection_capabilities.dart';import 'index.ng_deps.dart' as ngStaticInit0;
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();ngStaticInit0.initReflector(reflector);
bootstrap(MyComponent);
}
""";

View File

@ -0,0 +1,10 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(MyComponent);
}

View File

@ -0,0 +1,10 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(MyComponent);
}

View File

@ -0,0 +1,7 @@
Tests that the reflection removal step:
1. Comments out the import of reflection_capabilities.dart
2. Comments out the instantiation of `ReflectionCapabilities`
3. Adds the appropriate import.
4. Adds the call to `initReflector`
5. Does not change line numbers in the source.
6. Makes minimal changes to source offsets.

View File

@ -0,0 +1,22 @@
library angular2.test.transform.log_mirrors_files.expected;
// This file is intentionally formatted as a string to avoid having the
// automatic transformer prettify it.
//
// This file represents transformed user code. Because this code will be
// linked to output by a source map, we cannot change line numbers from the
// original code and we therefore add our generated code on the same line as
// those we are removing.
var code = """
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
/*import 'package:angular2/src/reflection/reflection_capabilities.dart';*/
void main() {
/*reflector.reflectionCapabilities = new ReflectionCapabilities();*/
bootstrap(MyComponent);
}
""";

View File

@ -0,0 +1,10 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(MyComponent);
}

View File

@ -0,0 +1,22 @@
library angular2.test.transform.debug_reflection_remover_files;
// This file is intentionally formatted as a string to avoid having the
// automatic transformer prettify it.
//
// This file represents transformed user code. Because this code will be
// linked to output by a source map, we cannot change line numbers from the
// original code and we therefore add our generated code on the same line as
// those we are removing.
var code = """
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/debug_reflection_capabilities.dart';import 'index.ng_deps.dart' as ngStaticInit0;
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities(verbose: true);ngStaticInit0.initReflector(reflector);
bootstrap(MyComponent);
}
""";