Skip to content

Commit 9b2da73

Browse files
Pete Bloispavelgj
Pete Blois
authored andcommitted
feat(transformer): pub transformer for automatically creating static type factories
1 parent b240ed8 commit 9b2da73

21 files changed

+1456
-13
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ test/main.dart.js
77
test/main.dart.js.deps
88
test/main.dart.js.map
99
test/main.dart.precompiled.js
10+
test/transformer/build
1011
out
11-
12+
build

lib/auto_injector.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Library for using a pub transformer to automatically switch between
3+
* dynamic and static injection.
4+
*
5+
* ## Step 1: Hook up the build step
6+
* Edit ```pubspec.yaml``` to add the di transformer to the list of
7+
* transformers.
8+
*
9+
* name: transformer_demo
10+
* version: 0.0.1
11+
* dependencies:
12+
* di: any
13+
* inject: any
14+
* transformers:
15+
* - di:
16+
* dart_entry: web/main.dart
17+
* injectable_annotations: transformer_demo.Injectable
18+
*
19+
* ## Step 2: Annotate your types
20+
*
21+
* class Engine {
22+
* @inject
23+
* Engine();
24+
* }
25+
*
26+
* or
27+
*
28+
* @Injectable // custom annotation specified in pubspec.yaml
29+
* class Car {}
30+
*
31+
*
32+
* ## Step 3: Use the auto injector
33+
* Modify your entry script to use [defaultInjector] as the injector.
34+
*
35+
* This must be done from the file registered as the dart_entry in pubspec.yaml
36+
* as this is the only file which will be modified to include the generated
37+
* injector.
38+
*
39+
* import 'package:di/auto_injector' as auto;
40+
* main() {
41+
* var injector = auto.defaultInjector(modules: ...);
42+
* }
43+
}
44+
*/
45+
library di.auto_injector;
46+
47+
import 'package:di/di.dart';
48+
import 'package:di/dynamic_injector.dart';
49+
50+
@MirrorsUsed(override: '*')
51+
import 'dart:mirrors' show MirrorsUsed;
52+
53+
Injector defaultInjector({List<Module> modules, String name,
54+
bool allowImplicitInjection: false}) =>
55+
new DynamicInjector(
56+
modules: modules,
57+
name: name,
58+
allowImplicitInjection: allowImplicitInjection);

lib/generator.dart

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
77
import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk;
88
import 'package:analyzer/src/generated/element.dart';
99
import 'package:analyzer/src/generated/engine.dart';
10+
import 'package:path/path.dart' as path;
1011

1112
import 'dart:io';
1213

@@ -31,7 +32,7 @@ main(args) {
3132
print('output: $output');
3233
print('packageRoots: $packageRoots');
3334

34-
var code = generateCode(entryPoint, classAnnotations, pathToSdk, packageRoots);
35+
var code = generateCode(entryPoint, classAnnotations, pathToSdk, packageRoots, output);
3536
code.forEach((chunk, code) {
3637
String fileName = output;
3738
if (chunk.library != null) {
@@ -43,14 +44,14 @@ main(args) {
4344
}
4445

4546
Map<Chunk, String> generateCode(String entryPoint, List<String> classAnnotations,
46-
String pathToSdk, List<String> packageRoots) {
47+
String pathToSdk, List<String> packageRoots, String outputFilename) {
4748
var c = new SourceCrawler(pathToSdk, packageRoots);
4849
List<String> imports = <String>[];
4950
Map<Chunk, List<ClassElement>> typeFactoryTypes = <Chunk, List<ClassElement>>{};
5051
Map<String, String> typeToImport = new Map<String, String>();
5152
c.crawl(entryPoint, (CompilationUnitElement compilationUnit, SourceFile source) {
5253
new CompilationUnitVisitor(c.context, source, classAnnotations, imports,
53-
typeToImport, typeFactoryTypes).visit(compilationUnit, source);
54+
typeToImport, typeFactoryTypes, outputFilename).visit(compilationUnit, source);
5455
});
5556
return printLibraryCode(typeToImport, imports, typeFactoryTypes);
5657
}
@@ -131,10 +132,11 @@ class CompilationUnitVisitor {
131132
List<String> classAnnotations;
132133
SourceFile source;
133134
AnalysisContext context;
135+
String outputFilename;
134136

135137
CompilationUnitVisitor(this.context, this.source,
136138
this.classAnnotations, this.imports, this.typeToImport,
137-
this.typeFactoryTypes);
139+
this.typeFactoryTypes, this.outputFilename);
138140

139141
visit(CompilationUnitElement compilationUnit, SourceFile source) {
140142
visitLibrary(compilationUnit.enclosingElement, source);
@@ -188,10 +190,13 @@ class CompilationUnitVisitor {
188190
if (classElement.name.startsWith('_')) {
189191
return; // ignore private classes.
190192
}
191-
typeToImport[getCanonicalName(classElement.type)] =
192-
source.entryPointImport;
193-
if (!imports.contains(source.entryPointImport)) {
194-
imports.add(source.entryPointImport);
193+
var importUri = source.entryPointImport;
194+
if (Uri.parse(importUri).scheme == '') {
195+
importUri = path.relative(importUri, from: path.dirname(outputFilename));
196+
}
197+
typeToImport[getCanonicalName(classElement.type)] = importUri;
198+
if (!imports.contains(importUri)) {
199+
imports.add(importUri);
195200
}
196201
for (ElementAnnotation ann in classElement.metadata) {
197202
if (ann.element is ConstructorElement) {

lib/transformer.dart

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* Transformer which generates type factories for static injection.
3+
*
4+
* Types which are considered injectable can be annotated in the following ways:
5+
*
6+
* * Use the @inject annotation on a class from `package:inject/inject.dart`
7+
* @inject
8+
* class Engine {}
9+
*
10+
* or on the constructor:
11+
*
12+
* class Engine {
13+
* @inject
14+
* Engine();
15+
* }
16+
*
17+
* * Define a custom annotation in pubspec.yaml
18+
*
19+
* transformers:
20+
* - di:
21+
* injectable_annotations:
22+
* - library_name.ClassName
23+
* - library_name.constInstance
24+
*
25+
* Annotate constructors or classes with those annotations
26+
*
27+
* @ClassName()
28+
* class Engine {}
29+
*
30+
* class Engine {
31+
* @constInstance
32+
* Engine();
33+
* }
34+
*
35+
* * Use package:di's Injectables
36+
*
37+
* @Injectables(const [Engine])
38+
* library my_lib;
39+
*
40+
* import 'package:di/annotations.dart';
41+
*
42+
* class Engine {}
43+
*
44+
* * Specify injected types via pubspec.yaml
45+
*
46+
* transformers:
47+
* - di:
48+
* injected_types:
49+
* - library_name.Engine
50+
*/
51+
library di.transformer;
52+
53+
import 'dart:io';
54+
import 'package:barback/barback.dart';
55+
import 'package:code_transformers/resolver.dart';
56+
import 'package:di/transformer/injector_generator.dart';
57+
import 'package:di/transformer/options.dart';
58+
import 'package:path/path.dart' as path;
59+
60+
61+
/**
62+
* The transformer, which will extract all classes being dependency injected
63+
* into a static injector.
64+
*/
65+
class DependencyInjectorTransformerGroup implements TransformerGroup {
66+
final Iterable<Iterable> phases;
67+
68+
DependencyInjectorTransformerGroup(TransformOptions options)
69+
: phases = _createPhases(options);
70+
71+
DependencyInjectorTransformerGroup.asPlugin(BarbackSettings settings)
72+
: this(_parseSettings(settings.configuration));
73+
}
74+
75+
TransformOptions _parseSettings(Map args) {
76+
var annotations = _readStringListValue(args, 'injectable_annotations');
77+
var injectedTypes = _readStringListValue(args, 'injected_types');
78+
79+
var sdkDir = _readStringValue(args, 'dart_sdk', required: false);
80+
if (sdkDir == null) {
81+
// Assume the Pub executable is always coming from the SDK.
82+
sdkDir = path.dirname(path.dirname(Platform.executable));
83+
}
84+
85+
return new TransformOptions(
86+
dartEntries: _readStringListValue(args, 'dart_entries'),
87+
injectableAnnotations: annotations,
88+
injectedTypes: injectedTypes,
89+
sdkDirectory: sdkDir);
90+
}
91+
92+
_readStringValue(Map args, String name, {bool required: true}) {
93+
var value = args[name];
94+
if (value == null) {
95+
if (required) {
96+
print('di transformer "$name" has no value.');
97+
}
98+
return null;
99+
}
100+
if (value is! String) {
101+
print('di transformer "$name" value is not a string.');
102+
return null;
103+
}
104+
return value;
105+
}
106+
107+
_readStringListValue(Map args, String name) {
108+
var value = args[name];
109+
if (value == null) return [];
110+
var results = [];
111+
bool error;
112+
if (value is List) {
113+
results = value;
114+
error = value.any((e) => e is! String);
115+
} else if (value is String) {
116+
results = [value];
117+
error = false;
118+
} else {
119+
error = true;
120+
}
121+
if (error) {
122+
print('Invalid value for "$name" in di transformer .');
123+
}
124+
return results;
125+
}
126+
127+
List<List<Transformer>> _createPhases(TransformOptions options) =>
128+
[[new InjectorGenerator(options, new Resolvers(options.sdkDirectory))]];

0 commit comments

Comments
 (0)