|
| 1 | +import 'dart:io'; |
| 2 | +import 'dart:convert'; |
| 3 | +import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; |
| 4 | +import 'package:analyzer/dart/analysis/results.dart'; |
| 5 | +import 'package:analyzer/error/error.dart'; |
| 6 | +import 'package:analyzer/file_system/physical_file_system.dart'; |
| 7 | +import 'package:analyzer/file_system/overlay_file_system.dart'; |
| 8 | +import 'package:analyzer/dart/element/visitor.dart'; |
| 9 | +import 'package:analyzer/dart/element/element.dart'; |
| 10 | +import 'package:analyzer/dart/element/type.dart'; |
| 11 | +import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| 12 | + |
| 13 | +final Set<String> libsToDo = {}; |
| 14 | + |
| 15 | +final Set<String> libsDone = {}; |
| 16 | + |
| 17 | +final Map<String, String> packages = {}; |
| 18 | + |
| 19 | +String libPathToPackageName(String path) { |
| 20 | + if (packages[path] != null) return packages[path] as String; |
| 21 | + for (final pname in packages.keys) { |
| 22 | + if (path.startsWith(pname)) { |
| 23 | + String pack = packages[pname] as String; |
| 24 | + String result = path.replaceRange(0, pname.length, pack); |
| 25 | + packages[path] = result; |
| 26 | + return result; |
| 27 | + } |
| 28 | + } |
| 29 | + return path; |
| 30 | +} |
| 31 | + |
| 32 | +bool isPublic(Element e) => e.isPublic; |
| 33 | + |
| 34 | +R fnil<R,E>(R f(E e), E? x, R fallback) => x != null ? f(x) : fallback; |
| 35 | + |
| 36 | +String M(Map<String,dynamic> m) { |
| 37 | + final sb = StringBuffer("{"); |
| 38 | + var first = true; |
| 39 | + m.forEach((k, v) { |
| 40 | + if (v == null || v == false || v is Iterable && v.isEmpty) return; |
| 41 | + if (first) first=false; else sb.write("\n"); |
| 42 | + sb..write(k)..write(" "); |
| 43 | + if (v is Iterable) { |
| 44 | + sb..write("[") |
| 45 | + ..writeAll(v, " ") |
| 46 | + ..write("]"); |
| 47 | + return; |
| 48 | + } |
| 49 | + sb.write(v); |
| 50 | + }); |
| 51 | + sb.write("}"); |
| 52 | + return sb.toString(); |
| 53 | +} |
| 54 | + |
| 55 | +String emitType(DartType t) { |
| 56 | + final lib = t.element?.library?.identifier; |
| 57 | + if (lib != null) libsToDo.add(lib); |
| 58 | + if (t is FunctionType) { |
| 59 | + return M({':type': "\"Function\"", |
| 60 | + ':return-type': emitType(t.returnType), |
| 61 | + ':parameters': t.parameters.map(emitParameter), |
| 62 | + ':type-parameters': t.typeFormals.map(emitTypeParameter)}); |
| 63 | + } |
| 64 | + var name = t.displayName; |
| 65 | + final i = name.indexOf("<"); |
| 66 | + if (i >= 0) name = name.substring(0, i); |
| 67 | + final isParam = t is TypeParameterType; |
| 68 | + //if (!isParam || lib != null) addLibIdentifierIfNotContains(libsToDo, lib as String); |
| 69 | + return M({':type': "\"${name}\"", |
| 70 | + ':nullable': t.isDartCoreNull || t.nullabilitySuffix == NullabilitySuffix.question, |
| 71 | + ':is-param': isParam, |
| 72 | + ':lib': (isParam || lib == null ? null : "\"${libPathToPackageName(lib)}\""), |
| 73 | + ':type-parameters': t is ParameterizedType ? t.typeArguments.map(emitType) : null |
| 74 | + }); |
| 75 | +} |
| 76 | + |
| 77 | +String emitTypeParameter(TypeParameterElement tp) => |
| 78 | +M({':type': "\"${tp.displayName}\"", |
| 79 | + ':is-param': true, |
| 80 | + ':bound': fnil(emitType,tp.bound,null)}); |
| 81 | + |
| 82 | +String emitParameter(ParameterElement p) { |
| 83 | + final name = p.displayName; |
| 84 | + return M({":name": name.isEmpty ? null : name, ":kind": p.isNamed ? ':named' : ':positional', ':type': emitType(p.type), ':optional': p.isOptional}); |
| 85 | +} |
| 86 | + |
| 87 | +class TopLevelVisitor extends ThrowingElementVisitor { |
| 88 | + void visitImportElement(ImportElement e) { |
| 89 | + print(e.displayName); |
| 90 | + throw "coucou"; |
| 91 | + } |
| 92 | + |
| 93 | + void visitClassElement(ClassElement e) { |
| 94 | + print("\"${e.displayName}\""); |
| 95 | + Map<String,dynamic> classData = |
| 96 | + {':kind': ':class', |
| 97 | + ':lib': '"${libPathToPackageName(e.library.identifier)}"', |
| 98 | + ':private': e.isPrivate, |
| 99 | + ':internal': e.hasInternal, |
| 100 | + ':const': e.unnamedConstructor?.isConst, |
| 101 | + ':type-parameters': e.typeParameters.map(emitTypeParameter), |
| 102 | + ':super': fnil(emitType,e.supertype,null), |
| 103 | + ':mixins': e.mixins.map(emitType), |
| 104 | + ':interfaces': e.interfaces.map(emitType), |
| 105 | + ':on': e.superclassConstraints.map(emitType)}; |
| 106 | + for(final m in e.methods.where(isPublic)) { |
| 107 | + final name = m.displayName; |
| 108 | + classData["\"${name == '-' && m.parameters.isEmpty ? 'unary-' : name}\""]= |
| 109 | + M({':kind': ':method', ':operator': m.isOperator, |
| 110 | + ':static': m.isStatic, |
| 111 | + ':return-type': emitType(m.returnType), |
| 112 | + ':parameters': m.parameters.map(emitParameter), |
| 113 | + ':type-parameters': m.typeParameters.map(emitTypeParameter) |
| 114 | + }); |
| 115 | + } |
| 116 | + for(final c in e.constructors.where(isPublic)) { |
| 117 | + classData["\"${c.displayName}\""]= |
| 118 | + M({':kind': ':constructor', |
| 119 | + ':return-type': emitType(c.returnType), |
| 120 | + ':parameters': c.parameters.map(emitParameter), |
| 121 | + ':type-parameters': c.typeParameters.map(emitTypeParameter) |
| 122 | + }); |
| 123 | + } |
| 124 | + for(final f in e.fields.where(isPublic)) { |
| 125 | + classData["\"${f.displayName}\""]= |
| 126 | + M({':kind': ':field', |
| 127 | + ':static': f.isStatic, |
| 128 | + ':getter': f.getter!=null, |
| 129 | + ':setter': f.setter!=null, |
| 130 | + ':type': emitType(f.type) |
| 131 | + }); |
| 132 | + } |
| 133 | + print(M(classData)); |
| 134 | + } |
| 135 | + void visitPropertyAccessorElement(PropertyAccessorElement e) { |
| 136 | + print("; getter/setter ${e.displayName}"); |
| 137 | + } |
| 138 | + void visitTopLevelVariableElement(TopLevelVariableElement e) { |
| 139 | + print("\"${e.displayName}\""); |
| 140 | + print(M({':kind': ':field', |
| 141 | + ':const': e.isConst, |
| 142 | + ':getter': e.getter!=null, |
| 143 | + ':setter': e.setter!=null, |
| 144 | + ':type': emitType(e.type) |
| 145 | + })); |
| 146 | + } |
| 147 | + void visitTypeAliasElement(TypeAliasElement e) { |
| 148 | + print("; typedef ${e.displayName}"); |
| 149 | + } |
| 150 | + void visitFunctionElement(FunctionElement e) { |
| 151 | + print("\"${e.displayName}\""); |
| 152 | + Map<String,dynamic> classData = |
| 153 | + {':kind': ':function', |
| 154 | + ':lib': '"${libPathToPackageName(e.library.identifier)}"', |
| 155 | + ':parameters': e.parameters.map(emitParameter), |
| 156 | + ':return-type': emitType(e.returnType), |
| 157 | + ':type-parameters': e.typeParameters.map(emitTypeParameter)}; |
| 158 | + print(M(classData)); |
| 159 | + } |
| 160 | + void visitExtensionElement(ExtensionElement e) { |
| 161 | + print("; extension ${e.displayName}"); |
| 162 | + } |
| 163 | +} |
| 164 | + |
| 165 | +Future<void> analyzePaths (session, List<String> paths) async { |
| 166 | + for (final p in paths) { |
| 167 | + final libraryElementResult = await session.getLibraryByUri(p); |
| 168 | + if (!libsDone.add(p)) continue; |
| 169 | + if (libraryElementResult is LibraryElementResult) { |
| 170 | + final libraryElement = (libraryElementResult as LibraryElementResult).element; |
| 171 | + final packageName = libPathToPackageName(libraryElement.identifier); |
| 172 | + if (packageName != p) { |
| 173 | + libsToDo.add(packageName); |
| 174 | + continue; |
| 175 | + } |
| 176 | + print("\"$p\" {"); // open 1 |
| 177 | + for (final top in libraryElement.topLevelElements) { |
| 178 | + if (top.isPublic) top.accept(TopLevelVisitor()); |
| 179 | + } |
| 180 | + var exs = libraryElement.exports; |
| 181 | + if (exs.isNotEmpty) { |
| 182 | + print(":exports ["); |
| 183 | + for (final ex in libraryElement.exports) { |
| 184 | + if (ex.exportedLibrary != null) { |
| 185 | + var n = ex.exportedLibrary?.identifier as String; |
| 186 | + libsToDo.add(n); |
| 187 | + for (final comb in ex.combinators) { |
| 188 | + if (comb is ShowElementCombinator) { |
| 189 | + print(M({":lib": "\"${libPathToPackageName(n)}\"", ':shown': comb.shownNames.map((name) => "\"${name}\"").toList()})); |
| 190 | + } |
| 191 | + if (comb is HideElementCombinator) { |
| 192 | + print(M({":lib": "\"${libPathToPackageName(n)}\"", ':hidden': comb.hiddenNames.map((name) => "\"${name}\"").toList()})); |
| 193 | + } |
| 194 | + } |
| 195 | + if (ex.combinators.isEmpty) { |
| 196 | + print(M({":lib": "\"${n}\""})); |
| 197 | + } |
| 198 | + } |
| 199 | + } |
| 200 | + print("]"); |
| 201 | + } |
| 202 | + print(":private ${libraryElement.isPrivate}"); |
| 203 | + print(":internal ${libraryElement.hasInternal}"); |
| 204 | + print("}"); // close 1 |
| 205 | + } |
| 206 | + } |
| 207 | +} |
| 208 | + |
| 209 | +void main(args) async { |
| 210 | + final resourceProvider = OverlayResourceProvider(PhysicalResourceProvider.INSTANCE); |
| 211 | + var ctx = resourceProvider.pathContext; |
| 212 | + late Directory dir; |
| 213 | + if (args.isEmpty) dir = Directory.current; |
| 214 | + else dir = Directory(args.first); |
| 215 | + Uri projectDirectoryUri = dir.uri; |
| 216 | + final collection = AnalysisContextCollection( |
| 217 | + includedPaths: [ctx.normalize(projectDirectoryUri.path)], |
| 218 | + resourceProvider: resourceProvider |
| 219 | + ); |
| 220 | + final includedDependenciesPaths = <String>[]; |
| 221 | + for (final context in collection.contexts) { |
| 222 | + final currentSession = context.currentSession; |
| 223 | + var packageFileJsonString = context.contextRoot.packagesFile?.readAsStringSync(); |
| 224 | + // TODO throw when package file does not exist |
| 225 | + if (packageFileJsonString != null) { |
| 226 | + var jsonContent = json.decode(packageFileJsonString); |
| 227 | + for (final jc in jsonContent["packages"]) { |
| 228 | + var rootUri = jc["rootUri"]; |
| 229 | + if (jc.containsKey("packageUri")) { |
| 230 | + rootUri = ctx.join(rootUri, jc["packageUri"]); |
| 231 | + } |
| 232 | + packages[rootUri] = 'package:' + jc["name"] + '/'; |
| 233 | + String normalizedPath = ctx.normalize(ctx.fromUri(rootUri)); |
| 234 | + if (!ctx.isRelative(normalizedPath)) { |
| 235 | + includedDependenciesPaths.add(normalizedPath); |
| 236 | + } else { |
| 237 | + stderr.write("WARNING: could not analyze '" + jc["name"] + "' package \n"); |
| 238 | + // TODO: better message on consequences |
| 239 | + } |
| 240 | + }; |
| 241 | + } |
| 242 | + } |
| 243 | + // NOTE : deps analysis |
| 244 | + final collectionDeps = AnalysisContextCollection( |
| 245 | + includedPaths: includedDependenciesPaths as List<String>, |
| 246 | + resourceProvider: resourceProvider |
| 247 | + ); |
| 248 | + print("{"); // open 2 |
| 249 | + for (final context in collectionDeps.contexts) { |
| 250 | + final currentSession = context.currentSession; |
| 251 | + var files = context.contextRoot.analyzedFiles().map((n) => ctx.toUri(n).toString()).toList(); |
| 252 | + files.removeWhere((p) => ctx.extension(p) != '.dart'); |
| 253 | + await analyzePaths(currentSession, files); |
| 254 | + } |
| 255 | + /// Only necessary for dart:* libs |
| 256 | + do { |
| 257 | + var libs = libsToDo.difference(libsDone); |
| 258 | + libsToDo..clear()..addAll(libs); |
| 259 | + await analyzePaths(collection.contexts.first.currentSession, libsToDo.toList()); |
| 260 | + } |
| 261 | + while(libsToDo.isNotEmpty); |
| 262 | + print("}"); // close 2 |
| 263 | +} |
0 commit comments