| OLD | NEW |
| (Empty) | |
| 1 library di.generator; |
| 2 |
| 3 import 'package:analyzer/src/generated/java_io.dart'; |
| 4 import 'package:analyzer/src/generated/source_io.dart'; |
| 5 import 'package:analyzer/src/generated/ast.dart'; |
| 6 import 'package:analyzer/src/generated/sdk.dart' show DartSdk; |
| 7 import 'package:analyzer/src/generated/sdk_io.dart' show DirectoryBasedDartSdk; |
| 8 import 'package:analyzer/src/generated/element.dart'; |
| 9 import 'package:analyzer/src/generated/engine.dart'; |
| 10 |
| 11 import 'dart:io'; |
| 12 |
| 13 const String PACKAGE_PREFIX = 'package:'; |
| 14 const String DART_PACKAGE_PREFIX = 'dart:'; |
| 15 |
| 16 main(args) { |
| 17 if (args.length < 4) { |
| 18 print('Usage: generator path_to_sdk file_to_resolve annotations output [pack
age_roots+]'); |
| 19 exit(0); |
| 20 } |
| 21 |
| 22 var pathToSdk = args[0]; |
| 23 var entryPoint = args[1]; |
| 24 var classAnnotations = args[2].split(','); |
| 25 var output = args[3]; |
| 26 var packageRoots = (args.length < 5) ? [Platform.packageRoot] : args.sublist(4
); |
| 27 |
| 28 print('pathToSdk: $pathToSdk'); |
| 29 print('entryPoint: $entryPoint'); |
| 30 print('classAnnotations: ${classAnnotations.join(', ')}'); |
| 31 print('output: $output'); |
| 32 print('packageRoots: $packageRoots'); |
| 33 |
| 34 var c = new SourceCrawler(pathToSdk, packageRoots); |
| 35 List<String> imports = <String>[]; |
| 36 List<ClassElement> typeFactoryTypes = <ClassElement>[]; |
| 37 Map<String, String> typeToImport = new Map<String, String>(); |
| 38 c.crawl(entryPoint, (CompilationUnitElement compilationUnit, SourceFile source
) { |
| 39 new CompilationUnitVisitor(c.context, source, classAnnotations, imports, |
| 40 typeToImport, typeFactoryTypes).visit(compilationUnit); |
| 41 }); |
| 42 var code = printLibraryCode(typeToImport, imports, typeFactoryTypes); |
| 43 new File(output).writeAsStringSync(code); |
| 44 } |
| 45 |
| 46 String printLibraryCode(Map<String, String> typeToImport, List<String> imports, |
| 47 List<ClassElement> typeFactoryTypes) { |
| 48 List<String> requiredImports = <String>[]; |
| 49 StringBuffer factories = new StringBuffer(); |
| 50 |
| 51 String resolveClassIdentifier(InterfaceType type) { |
| 52 if (type.element.library.isDartCore) { |
| 53 return type.name; |
| 54 } |
| 55 String import = typeToImport[getCanonicalName(type)]; |
| 56 if (!requiredImports.contains(import)) { |
| 57 requiredImports.add(import); |
| 58 } |
| 59 return 'import_${imports.indexOf(import)}.${type.name}'; |
| 60 } |
| 61 |
| 62 typeFactoryTypes.forEach((ClassElement clazz) { |
| 63 factories.write( |
| 64 'typeFactories[${resolveClassIdentifier(clazz.type)}] = (f) => '); |
| 65 factories.write('new ${resolveClassIdentifier(clazz.type)}('); |
| 66 ConstructorElement constr = |
| 67 clazz.constructors.firstWhere((c) => c.name.isEmpty, |
| 68 orElse: () { |
| 69 throw 'Unable to find default constructor for $clazz in ${clazz.source
}'; |
| 70 }); |
| 71 factories.write(constr.parameters.map((param) { |
| 72 if (param.type.element is! ClassElement) { |
| 73 throw 'Unable to resolve type for constructor parameter ' |
| 74 '"${param.name}" for type "$clazz" in ${clazz.source}'; |
| 75 } |
| 76 return 'f(${resolveClassIdentifier(param.type)})'; |
| 77 }).join(', ')); |
| 78 factories.write(');\n'); |
| 79 }); |
| 80 StringBuffer code = new StringBuffer(); |
| 81 code.write('library di.generated.type_factories;\n'); |
| 82 requiredImports.forEach((import) { |
| 83 code.write ('import "$import" as import_${imports.indexOf(import)};\n'); |
| 84 }); |
| 85 code..write('var typeFactories = new Map();\n') |
| 86 ..write('main() {\n') |
| 87 ..write(factories) |
| 88 ..write('}\n'); |
| 89 |
| 90 return code.toString(); |
| 91 } |
| 92 |
| 93 class CompilationUnitVisitor { |
| 94 List<String> imports; |
| 95 Map<String, String> typeToImport; |
| 96 List<ClassElement> typeFactoryTypes; |
| 97 List<String> classAnnotations; |
| 98 SourceFile source; |
| 99 AnalysisContext context; |
| 100 |
| 101 CompilationUnitVisitor(this.context, this.source, |
| 102 this.classAnnotations, this.imports, this.typeToImport, |
| 103 this.typeFactoryTypes); |
| 104 |
| 105 visit(CompilationUnitElement compilationUnit) { |
| 106 visitLibrary(compilationUnit.enclosingElement); |
| 107 |
| 108 List<ClassElement> types = <ClassElement>[]; |
| 109 types.addAll(compilationUnit.types); |
| 110 |
| 111 for (CompilationUnitElement part in compilationUnit.enclosingElement.parts)
{ |
| 112 types.addAll(part.types); |
| 113 } |
| 114 |
| 115 types.forEach(visitClassElement); |
| 116 } |
| 117 |
| 118 visitLibrary(LibraryElement libElement) { |
| 119 CompilationUnit resolvedUnit = context |
| 120 .resolveCompilationUnit(libElement.source, libElement); |
| 121 |
| 122 resolvedUnit.directives.forEach((Directive directive) { |
| 123 if (directive is LibraryDirective) { |
| 124 LibraryDirective library = directive; |
| 125 int annotationIdx = 0; |
| 126 library.metadata.forEach((Annotation ann) { |
| 127 if (ann.element is ConstructorElement && |
| 128 getQualifiedName( |
| 129 (ann.element as ConstructorElement).enclosingElement.type) == |
| 130 'di.annotations.Injectables') { |
| 131 var listLiteral = |
| 132 library.metadata[annotationIdx].arguments.arguments.first; |
| 133 for (Expression expr in listLiteral.elements) { |
| 134 Element element = (expr as SimpleIdentifier).bestElement; |
| 135 if (element == null || element is! ClassElement) { |
| 136 throw 'Unable to resolve type "$expr" from @Injectables ' |
| 137 'in ${library.element.source}'; |
| 138 } |
| 139 typeFactoryTypes.add(element as ClassElement); |
| 140 } |
| 141 } |
| 142 annotationIdx++; |
| 143 }); |
| 144 } |
| 145 }); |
| 146 } |
| 147 |
| 148 visitClassElement(ClassElement classElement) { |
| 149 if (classElement.name.startsWith('_')) { |
| 150 return; // ignore private classes. |
| 151 } |
| 152 typeToImport[getCanonicalName(classElement.type)] = |
| 153 source.entryPointImport; |
| 154 if (!imports.contains(source.entryPointImport)) { |
| 155 imports.add(source.entryPointImport); |
| 156 } |
| 157 for (ElementAnnotation ann in classElement.metadata) { |
| 158 if (ann.element is ConstructorElement) { |
| 159 ConstructorElement con = ann.element; |
| 160 if (classAnnotations |
| 161 .contains(getQualifiedName(con.enclosingElement.type))) { |
| 162 typeFactoryTypes.add(classElement); |
| 163 } |
| 164 } |
| 165 } |
| 166 } |
| 167 } |
| 168 |
| 169 String getQualifiedName(InterfaceType type) { |
| 170 var lib = type.element.library.displayName; |
| 171 var name = type.name; |
| 172 return lib == null ? name : '$lib.$name'; |
| 173 } |
| 174 |
| 175 String getCanonicalName(InterfaceType type) { |
| 176 var source = type.element.source.toString(); |
| 177 var name = type.name; |
| 178 return '$source:$name'; |
| 179 } |
| 180 |
| 181 typedef CompilationUnitCrawler(CompilationUnitElement compilationUnit, |
| 182 SourceFile source); |
| 183 |
| 184 class SourceCrawler { |
| 185 final List<String> packageRoots; |
| 186 final String sdkPath; |
| 187 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); |
| 188 |
| 189 SourceCrawler(this.sdkPath, this.packageRoots); |
| 190 |
| 191 void crawl(String entryPoint, CompilationUnitCrawler _visitor) { |
| 192 JavaSystemIO.setProperty("com.google.dart.sdk", sdkPath); |
| 193 DartSdk sdk = DirectoryBasedDartSdk.defaultSdk; |
| 194 |
| 195 AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); |
| 196 contextOptions.cacheSize = 256; |
| 197 contextOptions.preserveComments = false; |
| 198 contextOptions.analyzeFunctionBodies = false; |
| 199 context.analysisOptions = contextOptions; |
| 200 sdk.context.analysisOptions = contextOptions; |
| 201 |
| 202 var packageUriResolver = |
| 203 new PackageUriResolver(packageRoots.map( |
| 204 (pr) => new JavaFile.fromUri(new Uri.file(pr))).toList()); |
| 205 context.sourceFactory = new SourceFactory.con2([ |
| 206 new DartUriResolver(sdk), |
| 207 new FileUriResolver(), |
| 208 packageUriResolver |
| 209 ]); |
| 210 |
| 211 var entryPointFile; |
| 212 var entryPointImport; |
| 213 if (entryPoint.startsWith(PACKAGE_PREFIX)) { |
| 214 entryPointFile = new JavaFile(packageUriResolver |
| 215 .resolveAbsolute(context.sourceFactory.contentCache, |
| 216 Uri.parse(entryPoint)).toString()); |
| 217 entryPointImport = entryPoint; |
| 218 } else { |
| 219 entryPointFile = new JavaFile(entryPoint); |
| 220 entryPointImport = entryPointFile.getAbsolutePath(); |
| 221 } |
| 222 |
| 223 Source source = new FileBasedSource.con1( |
| 224 context.sourceFactory.contentCache, entryPointFile); |
| 225 ChangeSet changeSet = new ChangeSet(); |
| 226 changeSet.added(source); |
| 227 context.applyChanges(changeSet); |
| 228 LibraryElement rootLib = context.computeLibraryElement(source); |
| 229 CompilationUnit resolvedUnit = |
| 230 context.resolveCompilationUnit(source, rootLib); |
| 231 |
| 232 var sourceFile = new SourceFile( |
| 233 entryPointFile.getAbsolutePath(), |
| 234 entryPointImport, |
| 235 resolvedUnit.element); |
| 236 List<SourceFile> visited = <SourceFile>[]; |
| 237 List<SourceFile> toVisit = <SourceFile>[sourceFile]; |
| 238 |
| 239 while (toVisit.isNotEmpty) { |
| 240 SourceFile currentFile = toVisit.removeAt(0); |
| 241 visited.add(currentFile); |
| 242 _visitor(currentFile.compilationUnit, currentFile); |
| 243 var visitor = new CrawlerVisitor(currentFile, context); |
| 244 visitor.accept(currentFile.compilationUnit); |
| 245 visitor.toVisit.forEach((SourceFile todo) { |
| 246 if (!toVisit.contains(todo) && !visited.contains(todo)) { |
| 247 toVisit.add(todo); |
| 248 } |
| 249 }); |
| 250 } |
| 251 } |
| 252 } |
| 253 |
| 254 class CrawlerVisitor { |
| 255 List<SourceFile> toVisit = <SourceFile>[]; |
| 256 SourceFile currentFile; |
| 257 AnalysisContext context; |
| 258 String currentDir; |
| 259 |
| 260 CrawlerVisitor(this.currentFile, this.context); |
| 261 |
| 262 void accept(CompilationUnitElement cu) { |
| 263 cu.enclosingElement.imports.forEach((ImportElement import) => |
| 264 visitImportElement(import.uri, import.importedLibrary.source)); |
| 265 cu.enclosingElement.exports.forEach((ExportElement import) => |
| 266 visitImportElement(import.uri, import.exportedLibrary.source)); |
| 267 } |
| 268 |
| 269 visitImportElement(String uri, Source source) { |
| 270 if (uri == null) return; // dart:core |
| 271 |
| 272 String systemImport; |
| 273 bool isSystem = false; |
| 274 if (uri.startsWith(DART_PACKAGE_PREFIX)) { |
| 275 isSystem = true; |
| 276 systemImport = uri; |
| 277 } else if (currentFile.entryPointImport.startsWith(DART_PACKAGE_PREFIX)) { |
| 278 isSystem = true; |
| 279 systemImport = currentFile.entryPointImport; |
| 280 } |
| 281 // check if it's some internal hidden library |
| 282 if (isSystem && |
| 283 systemImport.substring(DART_PACKAGE_PREFIX.length).startsWith('_')) { |
| 284 return; |
| 285 } |
| 286 |
| 287 var nextCompilationUnit = context |
| 288 .resolveCompilationUnit(source, context.computeLibraryElement(source)); |
| 289 |
| 290 if (uri.startsWith(PACKAGE_PREFIX)) { |
| 291 toVisit.add(new SourceFile(source.toString(), uri, nextCompilationUnit.ele
ment)); |
| 292 } else { // relative import. |
| 293 var newImport; |
| 294 if (isSystem) { |
| 295 newImport = systemImport; // original uri |
| 296 } else { |
| 297 // relative import |
| 298 String import = currentFile.entryPointImport; |
| 299 import = import.replaceAll('\\', '/'); // if at all needed, on Windows |
| 300 import = import.substring(0, import.lastIndexOf('/')); |
| 301 var currentDir = new File(currentFile.canonicalPath).parent.path; |
| 302 currentDir = currentDir.replaceAll('\\', '/'); // if at all needed, on W
indows |
| 303 if (uri.startsWith('../')) { |
| 304 while (uri.startsWith('../')) { |
| 305 uri = uri.substring('../'.length); |
| 306 import = import.substring(0, import.lastIndexOf('/')); |
| 307 currentDir = currentDir.substring(0, currentDir.lastIndexOf('/')); |
| 308 } |
| 309 } |
| 310 newImport = '$import/$uri'; |
| 311 } |
| 312 toVisit.add(new SourceFile( |
| 313 source.toString(), newImport, nextCompilationUnit.element)); |
| 314 } |
| 315 } |
| 316 } |
| 317 |
| 318 class SourceFile { |
| 319 String canonicalPath; |
| 320 String entryPointImport; |
| 321 CompilationUnitElement compilationUnit; |
| 322 |
| 323 SourceFile(this.canonicalPath, this.entryPointImport, this.compilationUnit); |
| 324 |
| 325 operator ==(o) { |
| 326 if (o is String) return o == canonicalPath; |
| 327 if (o is! SourceFile) return false; |
| 328 return o.canonicalPath == canonicalPath; |
| 329 } |
| 330 } |
| OLD | NEW |