OLD | NEW |
1 library di.generator; | 1 library di.generator; |
2 | 2 |
3 import 'package:analyzer/src/generated/java_io.dart'; | 3 import 'package:analyzer/src/generated/java_io.dart'; |
4 import 'package:analyzer/src/generated/source_io.dart'; | 4 import 'package:analyzer/src/generated/source_io.dart'; |
5 import 'package:analyzer/src/generated/ast.dart'; | 5 import 'package:analyzer/src/generated/ast.dart'; |
6 import 'package:analyzer/src/generated/sdk.dart' show DartSdk; | 6 import 'package:analyzer/src/generated/sdk.dart' show DartSdk; |
7 import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; | 7 import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; |
8 import 'package:analyzer/src/generated/element.dart'; | 8 import 'package:analyzer/src/generated/element.dart'; |
9 import 'package:analyzer/src/generated/engine.dart'; | 9 import 'package:analyzer/src/generated/engine.dart'; |
| 10 import 'package:path/path.dart' as path; |
10 | 11 |
11 import 'dart:io'; | 12 import 'dart:io'; |
12 | 13 |
13 const String PACKAGE_PREFIX = 'package:'; | 14 const String PACKAGE_PREFIX = 'package:'; |
14 const String DART_PACKAGE_PREFIX = 'dart:'; | 15 const String DART_PACKAGE_PREFIX = 'dart:'; |
| 16 const List<String> _DEFAULT_INJECTABLE_ANNOTATIONS = |
| 17 const ['di.annotations.Injectable']; |
15 | 18 |
16 main(args) { | 19 main(List<String> args) { |
17 if (args.length < 4) { | 20 if (args.length < 4) { |
18 print('Usage: generator path_to_sdk file_to_resolve annotations output [pack
age_roots+]'); | 21 print('Usage: generator path_to_sdk file_to_resolve annotations output [pack
age_roots+]'); |
19 exit(0); | 22 exit(0); |
20 } | 23 } |
21 | 24 |
22 var pathToSdk = args[0]; | 25 var pathToSdk = args[0]; |
23 var entryPoint = args[1]; | 26 var entryPoint = args[1]; |
24 var classAnnotations = args[2].split(','); | 27 var classAnnotations = args[2].split(',') |
| 28 ..addAll(_DEFAULT_INJECTABLE_ANNOTATIONS); |
25 var output = args[3]; | 29 var output = args[3]; |
26 var packageRoots = (args.length < 5) ? [Platform.packageRoot] : args.sublist(4
); | 30 var packageRoots = (args.length < 5) ? [Platform.packageRoot] : args.sublist(4
); |
27 | 31 |
28 print('pathToSdk: $pathToSdk'); | 32 print('pathToSdk: $pathToSdk'); |
29 print('entryPoint: $entryPoint'); | 33 print('entryPoint: $entryPoint'); |
30 print('classAnnotations: ${classAnnotations.join(', ')}'); | 34 print('classAnnotations: ${classAnnotations.join(', ')}'); |
31 print('output: $output'); | 35 print('output: $output'); |
32 print('packageRoots: $packageRoots'); | 36 print('packageRoots: $packageRoots'); |
33 | 37 |
34 var code = generateCode(entryPoint, classAnnotations, pathToSdk, packageRoots)
; | 38 var code = generateCode(entryPoint, classAnnotations, pathToSdk, packageRoots,
output); |
35 code.forEach((chunk, code) { | 39 code.forEach((chunk, code) { |
36 String fileName = output; | 40 String fileName = output; |
37 if (chunk.library != null) { | 41 if (chunk.library != null) { |
38 var lastDot = fileName.lastIndexOf('.'); | 42 var lastDot = fileName.lastIndexOf('.'); |
39 fileName = fileName.substring(0, lastDot) + '-' + chunk.library.name + fil
eName.substring(lastDot); | 43 fileName = fileName.substring(0, lastDot) + '-' + chunk.library.name + fil
eName.substring(lastDot); |
40 } | 44 } |
41 new File(fileName).writeAsStringSync(code); | 45 new File(fileName).writeAsStringSync(code); |
42 }); | 46 }); |
43 } | 47 } |
44 | 48 |
45 Map<Chunk, String> generateCode(String entryPoint, List<String> classAnnotations
, | 49 Map<Chunk, String> generateCode(String entryPoint, List<String> classAnnotations
, |
46 String pathToSdk, List<String> packageRoots) { | 50 String pathToSdk, List<String> packageRoots, String outputFilename) { |
47 var c = new SourceCrawler(pathToSdk, packageRoots); | 51 var c = new SourceCrawler(pathToSdk, packageRoots); |
48 List<String> imports = <String>[]; | 52 List<String> imports = <String>[]; |
49 Map<Chunk, List<ClassElement>> typeFactoryTypes = <Chunk, List<ClassElement>>{
}; | 53 Map<Chunk, List<ClassElement>> typeFactoryTypes = <Chunk, List<ClassElement>>{
}; |
50 Map<String, String> typeToImport = new Map<String, String>(); | 54 Map<String, String> typeToImport = new Map<String, String>(); |
51 c.crawl(entryPoint, (CompilationUnitElement compilationUnit, SourceFile source
) { | 55 c.crawl(entryPoint, (CompilationUnitElement compilationUnit, SourceFile source
) { |
52 new CompilationUnitVisitor(c.context, source, classAnnotations, imports, | 56 new CompilationUnitVisitor(c.context, source, classAnnotations, imports, |
53 typeToImport, typeFactoryTypes).visit(compilationUnit, source); | 57 typeToImport, typeFactoryTypes, outputFilename).visit(compilationUnit,
source); |
54 }); | 58 }); |
55 return printLibraryCode(typeToImport, imports, typeFactoryTypes); | 59 return printLibraryCode(typeToImport, imports, typeFactoryTypes); |
56 } | 60 } |
57 | 61 |
58 Map<Chunk, String> printLibraryCode(Map<String, String> typeToImport, | 62 Map<Chunk, String> printLibraryCode(Map<String, String> typeToImport, |
59 List<String> imports, Map<Chunk, List<ClassElement>> typeFactoryTypes) { | 63 List<String> imports, Map<Chunk, List<ClassElement>> typeFactoryTypes) { |
60 Map<Chunk, StringBuffer> factories = <Chunk, StringBuffer>{}; | 64 Map<Chunk, StringBuffer> factories = <Chunk, StringBuffer>{}; |
61 Map<Chunk, String> result = <Chunk, String>{}; | 65 Map<Chunk, String> result = <Chunk, String>{}; |
62 typeFactoryTypes.forEach((Chunk chunk, List<ClassElement> classes) { | 66 typeFactoryTypes.forEach((Chunk chunk, List<ClassElement> classes) { |
63 List<String> requiredImports = <String>[]; | 67 List<String> requiredImports = <String>[]; |
64 String resolveClassIdentifier(InterfaceType type) { | 68 String resolveClassIdentifier(InterfaceType type) { |
65 if (type.element.library.isDartCore) { | 69 if (type.element.library.isDartCore) { |
66 return type.name; | 70 return type.name; |
67 } | 71 } |
68 String import = typeToImport[getCanonicalName(type)]; | 72 String import = typeToImport[getCanonicalName(type)]; |
69 if (!requiredImports.contains(import)) { | 73 if (!requiredImports.contains(import)) { |
70 requiredImports.add(import); | 74 requiredImports.add(import); |
71 } | 75 } |
72 return 'import_${imports.indexOf(import)}.${type.name}'; | 76 String prefix = _calculateImportPrefix(import, imports); |
| 77 return '$prefix.${type.name}'; |
73 } | 78 } |
74 factories[chunk] = new StringBuffer(); | 79 factories[chunk] = new StringBuffer(); |
75 classes.forEach((ClassElement clazz) { | 80 classes.forEach((ClassElement clazz) { |
76 StringBuffer factory = new StringBuffer(); | 81 StringBuffer factory = new StringBuffer(); |
77 bool skip = false; | 82 bool skip = false; |
78 factory.write( | 83 factory.write('${resolveClassIdentifier(clazz.type)}: (f) => '); |
79 '${resolveClassIdentifier(clazz.type)}: (f) => '); | |
80 factory.write('new ${resolveClassIdentifier(clazz.type)}('); | 84 factory.write('new ${resolveClassIdentifier(clazz.type)}('); |
81 ConstructorElement constr = | 85 ConstructorElement constr = |
82 clazz.constructors.firstWhere((c) => c.name.isEmpty, | 86 clazz.constructors.firstWhere((c) => c.name.isEmpty, |
83 orElse: () { | 87 orElse: () { |
84 throw 'Unable to find default constructor for $clazz in ${clazz.sour
ce}'; | 88 throw 'Unable to find default constructor for ' |
| 89 '$clazz in ${clazz.source}'; |
85 }); | 90 }); |
86 factory.write(constr.parameters.map((param) { | 91 factory.write(constr.parameters.map((param) { |
87 if (param.type.element is! ClassElement) { | 92 if (param.type.element is! ClassElement) { |
88 throw 'Unable to resolve type for constructor parameter ' | 93 throw 'Unable to resolve type for constructor parameter ' |
89 '"${param.name}" for type "$clazz" in ${clazz.source}'; | 94 '"${param.name}" for type "$clazz" in ${clazz.source}'; |
90 } | 95 } |
91 if (_isParameterized(param)) { | 96 if (_isParameterized(param)) { |
92 print('WARNING: parameterized types are not supported: $param in $claz
z in ${clazz.source}. Skipping!'); | 97 print('WARNING: parameterized types are not supported: ' |
| 98 '$param in $clazz in ${clazz.source}. Skipping!'); |
93 skip = true; | 99 skip = true; |
94 } | 100 } |
95 return 'f(${resolveClassIdentifier(param.type)})'; | 101 var annotations = []; |
| 102 if (param.metadata.isNotEmpty) { |
| 103 annotations = param.metadata.map( |
| 104 (item) => resolveClassIdentifier(item.element.returnType)); |
| 105 } |
| 106 StringBuffer output = |
| 107 new StringBuffer('f(${resolveClassIdentifier(param.type)}'); |
| 108 if (annotations.isNotEmpty) { |
| 109 output.write(', ${annotations.first}'); |
| 110 } |
| 111 output.write(')'); |
| 112 return output; |
96 }).join(', ')); | 113 }).join(', ')); |
97 factory.write('),\n'); | 114 factory.write('),\n'); |
98 if (!skip) { | 115 if (!skip) { |
99 factories[chunk].write(factory); | 116 factories[chunk].write(factory); |
100 } | 117 } |
101 }); | 118 }); |
102 StringBuffer code = new StringBuffer(); | 119 StringBuffer code = new StringBuffer(); |
103 String libSuffix = chunk.library == null ? '' : '.${chunk.library.name}'; | 120 String libSuffix = chunk.library == null ? '' : '.${chunk.library.name}'; |
104 code.write('library di.generated.type_factories$libSuffix;\n'); | 121 code.write('library di.generated.type_factories$libSuffix;\n'); |
105 requiredImports.forEach((import) { | 122 requiredImports.forEach((import) { |
106 code.write ('import "$import" as import_${imports.indexOf(import)};\n'); | 123 String prefix = _calculateImportPrefix(import, imports); |
| 124 code.write ('import "$import" as $prefix;\n'); |
107 }); | 125 }); |
108 code..write('var typeFactories = {\n${factories[chunk]}\n};\n') | 126 code..write('var typeFactories = {\n${factories[chunk]}\n};\n') |
109 ..write('main() {}\n'); | 127 ..write('main() {}\n'); |
110 result[chunk] = code.toString(); | 128 result[chunk] = code.toString(); |
111 }); | 129 }); |
112 | 130 |
113 return result; | 131 return result; |
114 } | 132 } |
115 | 133 |
| 134 String _calculateImportPrefix(String import, List<String> imports) => |
| 135 'import_${imports.indexOf(import)}'; |
| 136 |
116 _isParameterized(ParameterElement param) { | 137 _isParameterized(ParameterElement param) { |
117 String typeName = param.type.toString(); | 138 String typeName = param.type.toString(); |
118 | 139 |
119 if (typeName.indexOf('<') > -1) { | 140 if (typeName.indexOf('<') > -1) { |
120 String parameters = | 141 String parameters = |
121 typeName.substring(typeName.indexOf('<') + 1, typeName.length - 1); | 142 typeName.substring(typeName.indexOf('<') + 1, typeName.length - 1); |
122 return parameters.split(', ').any((p) => p != 'dynamic'); | 143 return parameters.split(', ').any((p) => p != 'dynamic'); |
123 } | 144 } |
124 return false; | 145 return false; |
125 } | 146 } |
126 | 147 |
127 class CompilationUnitVisitor { | 148 class CompilationUnitVisitor { |
128 List<String> imports; | 149 List<String> imports; |
129 Map<String, String> typeToImport; | 150 Map<String, String> typeToImport; |
130 Map<Chunk, List<ClassElement>> typeFactoryTypes; | 151 Map<Chunk, List<ClassElement>> typeFactoryTypes; |
131 List<String> classAnnotations; | 152 List<String> classAnnotations; |
132 SourceFile source; | 153 SourceFile source; |
133 AnalysisContext context; | 154 AnalysisContext context; |
| 155 String outputFilename; |
134 | 156 |
135 CompilationUnitVisitor(this.context, this.source, | 157 CompilationUnitVisitor(this.context, this.source, |
136 this.classAnnotations, this.imports, this.typeToImport, | 158 this.classAnnotations, this.imports, this.typeToImport, |
137 this.typeFactoryTypes); | 159 this.typeFactoryTypes, this.outputFilename); |
138 | 160 |
139 visit(CompilationUnitElement compilationUnit, SourceFile source) { | 161 visit(CompilationUnitElement compilationUnit, SourceFile source) { |
| 162 if (typeFactoryTypes[source.chunk] == null) { |
| 163 typeFactoryTypes[source.chunk] = <ClassElement>[]; |
| 164 } |
140 visitLibrary(compilationUnit.enclosingElement, source); | 165 visitLibrary(compilationUnit.enclosingElement, source); |
141 | 166 |
142 List<ClassElement> types = <ClassElement>[]; | 167 List<ClassElement> types = <ClassElement>[]; |
143 types.addAll(compilationUnit.types); | 168 types.addAll(compilationUnit.types); |
144 | 169 |
145 for (CompilationUnitElement part in compilationUnit.enclosingElement.parts)
{ | 170 for (CompilationUnitElement part in compilationUnit.enclosingElement.parts)
{ |
146 types.addAll(part.types); | 171 types.addAll(part.types); |
147 } | 172 } |
148 | 173 |
149 types.forEach((clazz) => visitClassElement(clazz, source)); | 174 types.forEach((clazz) => visitClassElement(clazz, source)); |
(...skipping 13 matching lines...) Expand all Loading... |
163 (ann.element as ConstructorElement).enclosingElement.type) == | 188 (ann.element as ConstructorElement).enclosingElement.type) == |
164 'di.annotations.Injectables') { | 189 'di.annotations.Injectables') { |
165 var listLiteral = | 190 var listLiteral = |
166 library.metadata[annotationIdx].arguments.arguments.first; | 191 library.metadata[annotationIdx].arguments.arguments.first; |
167 for (Expression expr in listLiteral.elements) { | 192 for (Expression expr in listLiteral.elements) { |
168 Element element = (expr as SimpleIdentifier).bestElement; | 193 Element element = (expr as SimpleIdentifier).bestElement; |
169 if (element == null || element is! ClassElement) { | 194 if (element == null || element is! ClassElement) { |
170 throw 'Unable to resolve type "$expr" from @Injectables ' | 195 throw 'Unable to resolve type "$expr" from @Injectables ' |
171 'in ${library.element.source}'; | 196 'in ${library.element.source}'; |
172 } | 197 } |
173 if (typeFactoryTypes[source.chunk] == null) { | |
174 typeFactoryTypes[source.chunk] = <ClassElement>[]; | |
175 } | |
176 if (!typeFactoryTypes[source.chunk].contains(element)) { | 198 if (!typeFactoryTypes[source.chunk].contains(element)) { |
177 typeFactoryTypes[source.chunk].add(element as ClassElement); | 199 typeFactoryTypes[source.chunk].add(element as ClassElement); |
178 } | 200 } |
179 } | 201 } |
180 } | 202 } |
181 annotationIdx++; | 203 annotationIdx++; |
182 }); | 204 }); |
183 } | 205 } |
184 }); | 206 }); |
185 } | 207 } |
186 | 208 |
187 visitClassElement(ClassElement classElement, SourceFile source) { | 209 visitClassElement(ClassElement classElement, SourceFile source) { |
188 if (classElement.name.startsWith('_')) { | 210 if (classElement.name.startsWith('_')) { |
189 return; // ignore private classes. | 211 return; // ignore private classes. |
190 } | 212 } |
191 typeToImport[getCanonicalName(classElement.type)] = | 213 var importUri = source.entryPointImport; |
192 source.entryPointImport; | 214 if (Uri.parse(importUri).scheme == '') { |
193 if (!imports.contains(source.entryPointImport)) { | 215 importUri = path.relative(importUri, from: path.dirname(outputFilename)); |
194 imports.add(source.entryPointImport); | 216 } |
| 217 typeToImport[getCanonicalName(classElement.type)] = importUri; |
| 218 if (!imports.contains(importUri)) { |
| 219 imports.add(importUri); |
195 } | 220 } |
196 for (ElementAnnotation ann in classElement.metadata) { | 221 for (ElementAnnotation ann in classElement.metadata) { |
197 if (ann.element is ConstructorElement) { | 222 if (ann.element is ConstructorElement) { |
198 ConstructorElement con = ann.element; | 223 ConstructorElement con = ann.element; |
199 if (classAnnotations | 224 if (classAnnotations |
200 .contains(getQualifiedName(con.enclosingElement.type))) { | 225 .contains(getQualifiedName(con.enclosingElement.type))) { |
201 if (typeFactoryTypes[source.chunk] == null) { | 226 if (typeFactoryTypes[source.chunk] == null) { |
202 typeFactoryTypes[source.chunk] = <ClassElement>[]; | 227 typeFactoryTypes[source.chunk] = <ClassElement>[]; |
203 } | 228 } |
204 if (!typeFactoryTypes[source.chunk].contains(classElement)) { | 229 if (!typeFactoryTypes[source.chunk].contains(classElement)) { |
(...skipping 20 matching lines...) Expand all Loading... |
225 typedef CompilationUnitCrawler(CompilationUnitElement compilationUnit, | 250 typedef CompilationUnitCrawler(CompilationUnitElement compilationUnit, |
226 SourceFile source); | 251 SourceFile source); |
227 | 252 |
228 class SourceCrawler { | 253 class SourceCrawler { |
229 final List<String> packageRoots; | 254 final List<String> packageRoots; |
230 final String sdkPath; | 255 final String sdkPath; |
231 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); | 256 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); |
232 | 257 |
233 SourceCrawler(this.sdkPath, this.packageRoots); | 258 SourceCrawler(this.sdkPath, this.packageRoots); |
234 | 259 |
235 void crawl(String entryPoint, CompilationUnitCrawler _visitor) { | 260 void crawl(String entryPoint, CompilationUnitCrawler _visitor, |
| 261 {bool preserveComments : false}) { |
236 JavaSystemIO.setProperty("com.google.dart.sdk", sdkPath); | 262 JavaSystemIO.setProperty("com.google.dart.sdk", sdkPath); |
237 DartSdk sdk = DirectoryBasedDartSdk.defaultSdk; | 263 DartSdk sdk = DirectoryBasedDartSdk.defaultSdk; |
238 | 264 |
239 AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); | 265 AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); |
240 contextOptions.cacheSize = 256; | 266 contextOptions.cacheSize = 256; |
241 contextOptions.preserveComments = false; | 267 contextOptions.preserveComments = preserveComments; |
242 contextOptions.analyzeFunctionBodies = false; | 268 contextOptions.analyzeFunctionBodies = false; |
243 context.analysisOptions = contextOptions; | 269 context.analysisOptions = contextOptions; |
244 sdk.context.analysisOptions = contextOptions; | 270 sdk.context.analysisOptions = contextOptions; |
245 | 271 |
246 var packageUriResolver = | 272 var packageUriResolver = |
247 new PackageUriResolver(packageRoots.map( | 273 new PackageUriResolver(packageRoots.map( |
248 (pr) => new JavaFile.fromUri(new Uri.file(pr))).toList()); | 274 (pr) => new JavaFile.fromUri(new Uri.file(pr))).toList()); |
249 context.sourceFactory = new SourceFactory.con2([ | 275 context.sourceFactory = new SourceFactory([ |
250 new DartUriResolver(sdk), | 276 new DartUriResolver(sdk), |
251 new FileUriResolver(), | 277 new FileUriResolver(), |
252 packageUriResolver | 278 packageUriResolver |
253 ]); | 279 ]); |
254 | 280 |
255 var entryPointFile; | 281 var entryPointFile; |
256 var entryPointImport; | 282 var entryPointImport; |
257 if (entryPoint.startsWith(PACKAGE_PREFIX)) { | 283 if (entryPoint.startsWith(PACKAGE_PREFIX)) { |
258 entryPointFile = new JavaFile(packageUriResolver | 284 entryPointFile = new JavaFile(packageUriResolver |
259 .resolveAbsolute(context.sourceFactory.contentCache, | 285 .resolveAbsolute(Uri.parse(entryPoint)).toString()); |
260 Uri.parse(entryPoint)).toString()); | |
261 entryPointImport = entryPoint; | 286 entryPointImport = entryPoint; |
262 } else { | 287 } else { |
263 entryPointFile = new JavaFile(entryPoint); | 288 entryPointFile = new JavaFile(entryPoint); |
264 entryPointImport = entryPointFile.getAbsolutePath(); | 289 entryPointImport = entryPointFile.getAbsolutePath(); |
265 } | 290 } |
266 | 291 |
267 Source source = new FileBasedSource.con1( | 292 Source source = new FileBasedSource.con1(entryPointFile); |
268 context.sourceFactory.contentCache, entryPointFile); | |
269 ChangeSet changeSet = new ChangeSet(); | 293 ChangeSet changeSet = new ChangeSet(); |
270 changeSet.added(source); | 294 changeSet.addedSource(source); |
271 context.applyChanges(changeSet); | 295 context.applyChanges(changeSet); |
272 LibraryElement rootLib = context.computeLibraryElement(source); | 296 LibraryElement rootLib = context.computeLibraryElement(source); |
273 CompilationUnit resolvedUnit = | 297 CompilationUnit resolvedUnit = |
274 context.resolveCompilationUnit(source, rootLib); | 298 context.resolveCompilationUnit(source, rootLib); |
275 | 299 |
276 var sourceFile = new SourceFile( | 300 var sourceFile = new SourceFile( |
277 entryPointFile.getAbsolutePath(), | 301 entryPointFile.getAbsolutePath(), |
278 entryPointImport, | 302 entryPointImport, |
279 resolvedUnit, | 303 resolvedUnit, |
280 resolvedUnit.element, | 304 resolvedUnit.element, |
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 | 490 |
467 SourceFile(this.canonicalPath, this.entryPointImport, this.compilationUnit, | 491 SourceFile(this.canonicalPath, this.entryPointImport, this.compilationUnit, |
468 this.compilationUnitElement, this.chunk); | 492 this.compilationUnitElement, this.chunk); |
469 | 493 |
470 operator ==(o) { | 494 operator ==(o) { |
471 if (o is String) return o == canonicalPath; | 495 if (o is String) return o == canonicalPath; |
472 if (o is! SourceFile) return false; | 496 if (o is! SourceFile) return false; |
473 return o.canonicalPath == canonicalPath; | 497 return o.canonicalPath == canonicalPath; |
474 } | 498 } |
475 } | 499 } |
OLD | NEW |