| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 import 'dart:convert'; |
| 6 import 'dart:io'; |
| 7 |
| 8 import 'package:analyzer/dart/ast/ast.dart'; |
| 9 import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
| 10 import 'package:analyzer/dart/ast/syntactic_entity.dart'; |
| 11 import 'package:analyzer/dart/ast/token.dart'; |
| 12 import 'package:analyzer/dart/ast/visitor.dart'; |
| 13 import 'package:analyzer/dart/element/element.dart'; |
| 14 import 'package:analyzer/dart/element/type.dart'; |
| 15 import 'package:analyzer/dart/element/visitor.dart'; |
| 16 import 'package:analyzer/src/dart/resolver/inheritance_manager.dart'; |
| 17 |
| 18 import 'schema.dart' as schema; |
| 19 |
| 20 const int _notFound = -1; |
| 21 |
| 22 /// Computes analysis of the given compilation [unit]. |
| 23 /// |
| 24 /// [unit] is the compilation unit to be analyzed; it is assumed to exist in the |
| 25 /// given [corpus] and to have the given text [contents]. Analysis results are |
| 26 /// returned as a list of Kythe [pb_Entry] objects. |
| 27 List<pb_Entry> computeIndex( |
| 28 String corpus, CompilationUnit unit, String contents) { |
| 29 final List<pb_Entry> entries = []; |
| 30 var visitor = new KytheDartVisitor( |
| 31 entries, |
| 32 corpus, |
| 33 new InheritanceManager( |
| 34 resolutionMap.elementDeclaredByCompilationUnit(unit).library), |
| 35 contents); |
| 36 unit.accept(visitor); |
| 37 return entries; |
| 38 } |
| 39 |
| 40 /// Outputs analysis of the given compilation [unit] to the parent process. |
| 41 /// |
| 42 /// [unit] is the compilation unit to be analyzed; it is assumed to exist in the |
| 43 /// given [corpus] and to have the given text [contents]. Analysis results are |
| 44 /// sent do the parent process as raw Kythe [Entry] objects. |
| 45 void writeOutIndex(CompilationUnit unit, String corpus, String contents) { |
| 46 List<pb_Entry> entries = computeIndex(corpus, unit, contents); |
| 47 for (pb_Entry e in entries) { |
| 48 assert(e.source != null); |
| 49 if (e.edgeKind == "") { |
| 50 assert(e.target.toString() == ""); |
| 51 } |
| 52 _sendToParentProcess(e); |
| 53 } |
| 54 } |
| 55 |
| 56 /// Given some [ConstructorElement], this method returns '<class-name>' as the |
| 57 /// name of the constructor, unless the constructor is a named constructor |
| 58 /// in which '<class-name>.<constructor-name>' is returned. |
| 59 String _computeConstructorElementName(ConstructorElement element) { |
| 60 assert(element != null); |
| 61 var name = element.enclosingElement.name; |
| 62 var constructorName = element.name; |
| 63 if (!constructorName.isEmpty) { |
| 64 name = name + '.' + constructorName; |
| 65 } |
| 66 return name; |
| 67 } |
| 68 |
| 69 /// Create an anchor signature of the form '<start>-<end>'. |
| 70 String _getAnchorSignature(int start, int end) { |
| 71 return '$start-$end'; |
| 72 } |
| 73 |
| 74 String _getPath(Element e) { |
| 75 // TODO(jwren) figure out what source generates a e != null, but |
| 76 // e.source == null to ensure that it is not a bug somewhere in the stack. |
| 77 if (e == null || e.source == null) { |
| 78 // null sometimes when the element is used to generate the node type |
| 79 // "dynamic" |
| 80 return ''; |
| 81 } |
| 82 var path = e.source.fullName; |
| 83 assert(path.lastIndexOf('CORPUS_NAME') != -1); |
| 84 return path.substring(path.lastIndexOf('CORPUS_NAME') + 12); |
| 85 } |
| 86 |
| 87 /// If a non-null element is passed, the [SignatureElementVisitor] is used to |
| 88 /// generate and return a [String] signature, otherwise |
| 89 /// [schema.DYNAMIC_KIND] is returned. |
| 90 String _getSignature(Element element, String nodeKind, String corpus) { |
| 91 assert(nodeKind != schema.ANCHOR_KIND); // Call _getAnchorSignature instead |
| 92 if (element == null) { |
| 93 return schema.DYNAMIC_KIND; |
| 94 } |
| 95 if (element is CompilationUnitElement) { |
| 96 return _getPath(element); |
| 97 } |
| 98 return '$nodeKind:${element.accept(SignatureElementVisitor.instance)}'; |
| 99 } |
| 100 |
| 101 /// Send a [Reply] message to the parent process via standard output. |
| 102 /// |
| 103 /// The message is formatted as a base-128 encoded length followed by the |
| 104 /// serialized message data. The base-128 encoding is in little-endian order, |
| 105 /// with the high bit set on all bytes but the last. This was chosen since |
| 106 /// it's the same as the base-128 encoding used by protobufs, so it allows a |
| 107 /// modest amount of code reuse. Also it parallels the format used by |
| 108 /// [messageGrouper]. |
| 109 void _sendToParentProcess(pb_Entry entry) { |
| 110 var rawMessage = entry.writeToBuffer(); |
| 111 var encodedLength = (new CodedBufferWriter(rawMessage.length)).toBuffer(); |
| 112 stdout..add(encodedLength)..add(rawMessage); |
| 113 } |
| 114 |
| 115 // TODO(jwren) This method simply serves to provide the WORKSPACE relative path |
| 116 // for sources in Elements, it needs to be written in a more robust way. |
| 117 /// This visitor writes out Kythe facts and edges as specified by the Kythe |
| 118 /// Schema here https://kythe.io/docs/schema/. This visitor handles all nodes, |
| 119 /// facts and edges. |
| 120 class KytheDartVisitor extends GeneralizingAstVisitor with OutputUtils { |
| 121 final List<pb_Entry> entries; |
| 122 final String corpus; |
| 123 final InheritanceManager _inheritanceManager; |
| 124 String _enclosingFilePath = ''; |
| 125 Element _enclosingElement; |
| 126 ClassElement _enclosingClassElement; |
| 127 pb_VName _enclosingVName; |
| 128 pb_VName _enclosingFileVName; |
| 129 pb_VName _enclosingClassVName; |
| 130 final String _contents; |
| 131 |
| 132 KytheDartVisitor( |
| 133 this.entries, this.corpus, this._inheritanceManager, this._contents); |
| 134 |
| 135 @override |
| 136 String get enclosingFilePath => _enclosingFilePath; |
| 137 |
| 138 @override |
| 139 visitAnnotation(Annotation node) { |
| 140 // TODO(jwren) To get the full set of cross refs correct, additional ref |
| 141 // edges are needed, example: from "A" in "A.namedConstructor()" |
| 142 |
| 143 var start = node.name.offset; |
| 144 var end = node.name.end; |
| 145 if (node.constructorName != null) { |
| 146 end = node.constructorName.end; |
| 147 } |
| 148 var refVName = _handleRefEdge( |
| 149 node.element, |
| 150 const <String>[schema.REF_EDGE], |
| 151 start: start, |
| 152 end: end, |
| 153 ); |
| 154 if (refVName != null) { |
| 155 var parentNode = node.parent; |
| 156 if (parentNode is Declaration) { |
| 157 Element parentElement = parentNode.element; |
| 158 if (parentNode is TopLevelVariableDeclaration) { |
| 159 _handleVariableDeclarationListAnnotations( |
| 160 parentNode.variables, refVName); |
| 161 } else if (parentNode is FieldDeclaration) { |
| 162 _handleVariableDeclarationListAnnotations( |
| 163 parentNode.fields, refVName); |
| 164 } else if (parentElement != null) { |
| 165 var parentVName = |
| 166 _vNameFromElement(parentElement, _getNodeKind(parentElement)); |
| 167 addEdge(parentVName, schema.ANNOTATED_BY_EDGE, refVName); |
| 168 } else { |
| 169 // parentAstNode is not a variable declaration node and |
| 170 // parentElement == null |
| 171 assert(false); |
| 172 } |
| 173 } else { |
| 174 // parentAstNode is not a Declaration |
| 175 // TODO(jwren) investigate |
| 176 // throw new Exception('parentAstNode.runtimeType = ${parentAstNode.runtime
Type}'); |
| 177 // assert(false); |
| 178 } |
| 179 } |
| 180 |
| 181 // visit children |
| 182 _safelyVisit(node.arguments); |
| 183 } |
| 184 |
| 185 @override |
| 186 visitAssignmentExpression(AssignmentExpression node) { |
| 187 // |
| 188 // operator |
| 189 // NOTE: usage node only written out if assignment is not the '=' operator, |
| 190 // we are looking for an operator such as +=, -=, *=, /= |
| 191 // |
| 192 Token operator = node.operator; |
| 193 MethodElement element = node.bestElement; |
| 194 if (operator.type != TokenType.EQ && element != null) { |
| 195 // method |
| 196 _vNameFromElement(element, schema.FUNCTION_KIND); |
| 197 |
| 198 // anchor- ref/call |
| 199 _handleRefCallEdge(element, |
| 200 syntacticEntity: node.operator, enclosingTarget: _enclosingVName); |
| 201 |
| 202 // TODO (jwren) Add function type information |
| 203 } |
| 204 // visit children |
| 205 _safelyVisit(node.leftHandSide); |
| 206 _safelyVisit(node.rightHandSide); |
| 207 } |
| 208 |
| 209 @override |
| 210 visitBinaryExpression(BinaryExpression node) { |
| 211 // |
| 212 // operators such as +, -, *, / |
| 213 // |
| 214 MethodElement element = node.bestElement; |
| 215 if (element != null) { |
| 216 // method |
| 217 _vNameFromElement(element, schema.FUNCTION_KIND); |
| 218 |
| 219 // anchor- ref/call |
| 220 _handleRefCallEdge(element, |
| 221 syntacticEntity: node.operator, enclosingTarget: _enclosingVName); |
| 222 |
| 223 // TODO (jwren) Add function type information |
| 224 } |
| 225 // visit children |
| 226 _safelyVisit(node.leftOperand); |
| 227 _safelyVisit(node.rightOperand); |
| 228 } |
| 229 |
| 230 @override |
| 231 visitClassDeclaration(ClassDeclaration node) { |
| 232 return _withEnclosingElement(node.element, () { |
| 233 // record/ class node |
| 234 addNodeAndFacts(schema.RECORD_KIND, |
| 235 nodeVName: _enclosingClassVName, |
| 236 subKind: schema.CLASS_SUBKIND, |
| 237 completeFact: schema.DEFINITION); |
| 238 |
| 239 // anchor- defines/binding |
| 240 addAnchorEdgesContainingEdge( |
| 241 syntacticEntity: node.name, |
| 242 edges: [ |
| 243 schema.DEFINES_BINDING_EDGE, |
| 244 ], |
| 245 target: _enclosingClassVName, |
| 246 enclosingTarget: _enclosingFileVName); |
| 247 |
| 248 // anchor- defines |
| 249 addAnchorEdgesContainingEdge( |
| 250 syntacticEntity: node, |
| 251 edges: [ |
| 252 schema.DEFINES_EDGE, |
| 253 ], |
| 254 target: _enclosingClassVName); |
| 255 |
| 256 // extends |
| 257 var supertype = _enclosingClassElement.supertype; |
| 258 if (supertype?.element != null) { |
| 259 var recordSupertypeVName = |
| 260 _vNameFromElement(supertype.element, schema.RECORD_KIND); |
| 261 addEdge( |
| 262 _enclosingClassVName, schema.EXTENDS_EDGE, recordSupertypeVName); |
| 263 } |
| 264 |
| 265 // implements |
| 266 var interfaces = _enclosingClassElement.interfaces; |
| 267 for (var interface in interfaces) { |
| 268 if (interface.element != null) { |
| 269 var recordInterfaceVName = |
| 270 _vNameFromElement(interface.element, schema.RECORD_KIND); |
| 271 addEdge( |
| 272 _enclosingClassVName, schema.EXTENDS_EDGE, recordInterfaceVName); |
| 273 } |
| 274 } |
| 275 |
| 276 // mixins |
| 277 var mixins = _enclosingClassElement.mixins; |
| 278 for (var mixin in mixins) { |
| 279 if (mixin.element != null) { |
| 280 var recordMixinVName = |
| 281 _vNameFromElement(mixin.element, schema.RECORD_KIND); |
| 282 addEdge(_enclosingClassVName, schema.EXTENDS_EDGE, recordMixinVName); |
| 283 } |
| 284 } |
| 285 |
| 286 // TODO (jwren) type parameters |
| 287 |
| 288 // visit children |
| 289 _safelyVisit(node.documentationComment); |
| 290 _safelyVisitList(node.metadata); |
| 291 _safelyVisit(node.extendsClause); |
| 292 _safelyVisit(node.implementsClause); |
| 293 _safelyVisit(node.withClause); |
| 294 _safelyVisit(node.nativeClause); |
| 295 _safelyVisitList(node.members); |
| 296 _safelyVisit(node.typeParameters); |
| 297 }); |
| 298 } |
| 299 |
| 300 @override |
| 301 visitClassTypeAlias(ClassTypeAlias node) { |
| 302 return _withEnclosingElement(node.element, () { |
| 303 // record/ class node |
| 304 addNodeAndFacts(schema.RECORD_KIND, |
| 305 nodeVName: _enclosingClassVName, |
| 306 subKind: schema.CLASS_SUBKIND, |
| 307 completeFact: schema.DEFINITION); |
| 308 |
| 309 // anchor |
| 310 addAnchorEdgesContainingEdge( |
| 311 syntacticEntity: node.name, |
| 312 edges: [ |
| 313 schema.DEFINES_BINDING_EDGE, |
| 314 ], |
| 315 target: _enclosingClassVName, |
| 316 enclosingTarget: _enclosingFileVName); |
| 317 |
| 318 // |
| 319 // superclass |
| 320 // The super type is not in an ExtendsClause (as is the case with |
| 321 // ClassDeclarations) and super.visitClassTypeAlias is not sufficient. |
| 322 // |
| 323 _handleRefEdge( |
| 324 node.superclass.name.bestElement, |
| 325 const <String>[schema.REF_EDGE], |
| 326 syntacticEntity: node.superclass, |
| 327 ); |
| 328 // TODO(jwren) refactor the following lines into a method that can be used |
| 329 // by visitClassDeclaration() |
| 330 // extends |
| 331 var recordSupertypeVName = _vNameFromElement( |
| 332 node.superclass.name.bestElement, schema.RECORD_KIND); |
| 333 addEdge(_enclosingClassVName, schema.EXTENDS_EDGE, recordSupertypeVName); |
| 334 |
| 335 // implements |
| 336 var interfaces = _enclosingClassElement.interfaces; |
| 337 for (var interface in interfaces) { |
| 338 if (interface.element != null) { |
| 339 var recordInterfaceVName = |
| 340 _vNameFromElement(interface.element, schema.RECORD_KIND); |
| 341 addEdge( |
| 342 _enclosingClassVName, schema.EXTENDS_EDGE, recordInterfaceVName); |
| 343 } |
| 344 } |
| 345 |
| 346 // mixins |
| 347 var mixins = _enclosingClassElement.mixins; |
| 348 for (var mixin in mixins) { |
| 349 if (mixin.element != null) { |
| 350 var recordMixinVName = |
| 351 _vNameFromElement(mixin.element, schema.RECORD_KIND); |
| 352 addEdge(_enclosingClassVName, schema.EXTENDS_EDGE, recordMixinVName); |
| 353 } |
| 354 } |
| 355 |
| 356 // visit children |
| 357 _safelyVisit(node.documentationComment); |
| 358 _safelyVisitList(node.metadata); |
| 359 _safelyVisit(node.typeParameters); |
| 360 _safelyVisit(node.withClause); |
| 361 _safelyVisit(node.implementsClause); |
| 362 }); |
| 363 } |
| 364 |
| 365 @override |
| 366 visitCompilationUnit(CompilationUnit node) { |
| 367 _enclosingFilePath = _getPath(node.element); |
| 368 return _withEnclosingElement(node.element, () { |
| 369 addFact(_enclosingFileVName, schema.NODE_KIND_FACT, |
| 370 _encode(schema.FILE_KIND)); |
| 371 addFact(_enclosingFileVName, schema.TEXT_FACT, _encode(_contents)); |
| 372 addFact(_enclosingFileVName, schema.TEXT_ENCODING_FACT, |
| 373 _encode(schema.DEFAULT_TEXT_ENCODING)); |
| 374 |
| 375 // handle LibraryDirective: |
| 376 |
| 377 // A "package" VName in Kythe, schema.PACKAGE_KIND, is a Dart "library". |
| 378 |
| 379 // Don't use visitLibraryDirective as this won't generate a package |
| 380 // VName for libraries that don't have a library directive. |
| 381 var libraryElement = |
| 382 resolutionMap.elementDeclaredByCompilationUnit(node).library; |
| 383 if (libraryElement.definingCompilationUnit == node.element) { |
| 384 LibraryDirective libraryDirective; |
| 385 for (var directive in node.directives) { |
| 386 if (directive is LibraryDirective) { |
| 387 libraryDirective = directive; |
| 388 break; |
| 389 } |
| 390 } |
| 391 |
| 392 var start = 0; |
| 393 var end = 0; |
| 394 if (libraryDirective != null) { |
| 395 start = libraryDirective.name.offset; |
| 396 end = libraryDirective.name.end; |
| 397 } |
| 398 |
| 399 // package node |
| 400 var packageVName = addNodeAndFacts(schema.PACKAGE_KIND, |
| 401 element: libraryElement, completeFact: schema.DEFINITION); |
| 402 |
| 403 // anchor |
| 404 addAnchorEdgesContainingEdge( |
| 405 start: start, |
| 406 end: end, |
| 407 edges: [ |
| 408 schema.DEFINES_BINDING_EDGE, |
| 409 ], |
| 410 target: packageVName, |
| 411 enclosingTarget: _enclosingFileVName); |
| 412 } |
| 413 |
| 414 super.visitCompilationUnit(node); |
| 415 }); |
| 416 } |
| 417 |
| 418 @override |
| 419 visitConstructorDeclaration(ConstructorDeclaration node) { |
| 420 return _withEnclosingElement(node.element, () { |
| 421 // function/ constructor node |
| 422 var constructorVName = addNodeAndFacts(schema.FUNCTION_KIND, |
| 423 element: node.element, |
| 424 subKind: schema.CONSTRUCTOR_SUBKIND, |
| 425 completeFact: schema.DEFINITION); |
| 426 |
| 427 // anchor |
| 428 var start = node.returnType.offset; |
| 429 var end = node.returnType.end; |
| 430 if (node.name != null) { |
| 431 end = node.name.end; |
| 432 } |
| 433 addAnchorEdgesContainingEdge( |
| 434 start: start, |
| 435 end: end, |
| 436 edges: [ |
| 437 schema.DEFINES_BINDING_EDGE, |
| 438 ], |
| 439 target: constructorVName, |
| 440 enclosingTarget: _enclosingClassVName); |
| 441 |
| 442 // function type |
| 443 addFunctionType(node.element, node.parameters, constructorVName, |
| 444 returnNode: node.returnType); |
| 445 |
| 446 // TODO(jwren) handle implicit constructor case |
| 447 // TODO(jwren) handle redirected constructor case |
| 448 |
| 449 // visit children |
| 450 _safelyVisit(node.documentationComment); |
| 451 _safelyVisitList(node.metadata); |
| 452 _safelyVisit(node.parameters); |
| 453 _safelyVisitList(node.initializers); |
| 454 _safelyVisit(node.body); |
| 455 }); |
| 456 } |
| 457 |
| 458 @override |
| 459 visitEnumConstantDeclaration(EnumConstantDeclaration node) { |
| 460 // constant node |
| 461 var constDeclVName = |
| 462 addNodeAndFacts(schema.CONSTANT_KIND, element: node.element); |
| 463 |
| 464 // anchor- defines/binding, defines |
| 465 addAnchorEdgesContainingEdge( |
| 466 syntacticEntity: node.name, |
| 467 edges: [ |
| 468 schema.DEFINES_BINDING_EDGE, |
| 469 schema.DEFINES_EDGE, |
| 470 ], |
| 471 target: constDeclVName, |
| 472 enclosingTarget: _enclosingClassVName); |
| 473 |
| 474 // no children |
| 475 } |
| 476 |
| 477 @override |
| 478 visitEnumDeclaration(EnumDeclaration node) { |
| 479 return _withEnclosingElement(node.element, () { |
| 480 // record/ enum node |
| 481 addNodeAndFacts(schema.RECORD_KIND, |
| 482 nodeVName: _enclosingClassVName, |
| 483 subKind: schema.ENUM_CLASS_SUBKIND, |
| 484 completeFact: schema.DEFINITION); |
| 485 |
| 486 // anchor- defines/binding |
| 487 addAnchorEdgesContainingEdge( |
| 488 syntacticEntity: node.name, |
| 489 edges: [ |
| 490 schema.DEFINES_BINDING_EDGE, |
| 491 ], |
| 492 target: _enclosingClassVName, |
| 493 enclosingTarget: _enclosingFileVName); |
| 494 |
| 495 // anchor- defines |
| 496 addAnchorEdgesContainingEdge( |
| 497 syntacticEntity: node, |
| 498 edges: [ |
| 499 schema.DEFINES_EDGE, |
| 500 ], |
| 501 target: _enclosingClassVName); |
| 502 |
| 503 // visit children |
| 504 _safelyVisitList(node.constants); |
| 505 }); |
| 506 } |
| 507 |
| 508 @override |
| 509 visitFieldFormalParameter(FieldFormalParameter node) { |
| 510 // identifier |
| 511 // Specified as Element, not var, so that the type can be changed in the |
| 512 // if-block. |
| 513 Element element = node.element; |
| 514 if (element is FieldFormalParameterElement) { |
| 515 element = (element as FieldFormalParameterElement).field; |
| 516 } |
| 517 _handleRefEdge( |
| 518 element, |
| 519 const <String>[schema.REF_EDGE], |
| 520 syntacticEntity: node.identifier, |
| 521 ); |
| 522 |
| 523 // visit children |
| 524 _safelyVisit(node.documentationComment); |
| 525 _safelyVisitList(node.metadata); |
| 526 _safelyVisit(node.type); |
| 527 _safelyVisit(node.typeParameters); |
| 528 _safelyVisit(node.parameters); |
| 529 } |
| 530 |
| 531 @override |
| 532 visitFunctionDeclaration(FunctionDeclaration node) { |
| 533 return _withEnclosingElement(node.element, () { |
| 534 // function node |
| 535 var functionVName = addNodeAndFacts(schema.FUNCTION_KIND, |
| 536 element: node.element, completeFact: schema.DEFINITION); |
| 537 |
| 538 // anchor- defines/binding |
| 539 addAnchorEdgesContainingEdge( |
| 540 syntacticEntity: node.name, |
| 541 edges: [ |
| 542 schema.DEFINES_BINDING_EDGE, |
| 543 ], |
| 544 target: functionVName, |
| 545 enclosingTarget: _enclosingFileVName); |
| 546 |
| 547 // anchor- defines |
| 548 addAnchorEdgesContainingEdge( |
| 549 syntacticEntity: node, |
| 550 edges: [ |
| 551 schema.DEFINES_EDGE, |
| 552 ], |
| 553 target: functionVName); |
| 554 |
| 555 // function type |
| 556 addFunctionType( |
| 557 node.element, node.functionExpression.parameters, functionVName, |
| 558 returnNode: node.returnType); |
| 559 |
| 560 _safelyVisit(node.documentationComment); |
| 561 _safelyVisitList(node.metadata); |
| 562 _safelyVisit(node.returnType); |
| 563 _safelyVisit(node.functionExpression); |
| 564 }); |
| 565 } |
| 566 |
| 567 @override |
| 568 visitFunctionExpression(FunctionExpression node) { |
| 569 return _withEnclosingElement( |
| 570 node.element, () => super.visitFunctionExpression(node)); |
| 571 } |
| 572 |
| 573 @override |
| 574 visitFunctionTypeAlias(FunctionTypeAlias node) { |
| 575 // |
| 576 // return type |
| 577 // |
| 578 var returnType = node.returnType; |
| 579 if (returnType is TypeName) { |
| 580 _handleRefEdge( |
| 581 returnType.name?.bestElement, |
| 582 const <String>[schema.REF_EDGE], |
| 583 syntacticEntity: returnType.name, |
| 584 ); |
| 585 } else if (returnType is GenericFunctionType) { |
| 586 // TODO(jwren): add support for generic function types. |
| 587 throw new UnimplementedError(); |
| 588 } else if (returnType != null) { |
| 589 throw new StateError( |
| 590 'Unexpected TypeAnnotation subtype: ${returnType.runtimeType}'); |
| 591 } |
| 592 |
| 593 // visit children |
| 594 _safelyVisit(node.documentationComment); |
| 595 _safelyVisitList(node.metadata); |
| 596 _safelyVisit(node.typeParameters); |
| 597 _safelyVisit(node.parameters); |
| 598 } |
| 599 |
| 600 @override |
| 601 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
| 602 // TODO(jwren) Missing graph coverage on FunctionTypedFormalParameters |
| 603 // visit children |
| 604 _safelyVisit(node.documentationComment); |
| 605 _safelyVisitList(node.metadata); |
| 606 _safelyVisit(node.identifier); |
| 607 _safelyVisit(node.typeParameters); |
| 608 _safelyVisit(node.parameters); |
| 609 } |
| 610 |
| 611 @override |
| 612 visitImportDirective(ImportDirective node) { |
| 613 // uri |
| 614 _handleUriReference(node.uri, node.uriElement); |
| 615 |
| 616 // prefix |
| 617 var prefixIdentifier = node.prefix; |
| 618 |
| 619 if (prefixIdentifier != null) { |
| 620 // variable |
| 621 var variableVName = addNodeAndFacts(schema.VARIABLE_KIND, |
| 622 element: prefixIdentifier.staticElement, |
| 623 completeFact: schema.DEFINITION); |
| 624 |
| 625 // anchor |
| 626 addAnchorEdgesContainingEdge( |
| 627 syntacticEntity: prefixIdentifier, |
| 628 edges: [schema.DEFINES_BINDING_EDGE], |
| 629 target: variableVName, |
| 630 enclosingTarget: _enclosingVName); |
| 631 } |
| 632 |
| 633 // visit children |
| 634 _safelyVisit(node.documentationComment); |
| 635 _safelyVisitList(node.metadata); |
| 636 _safelyVisitList(node.combinators); |
| 637 _safelyVisitList(node.configurations); |
| 638 _safelyVisit(node.uri); |
| 639 } |
| 640 |
| 641 @override |
| 642 visitIndexExpression(IndexExpression node) { |
| 643 // |
| 644 // index method ref/call |
| 645 // |
| 646 var element = node.bestElement; |
| 647 var start = node.leftBracket.offset; |
| 648 var end = node.rightBracket.end; |
| 649 |
| 650 // anchor- ref/call |
| 651 _handleRefCallEdge(element, |
| 652 start: start, end: end, enclosingTarget: _enclosingVName); |
| 653 |
| 654 // visit children |
| 655 _safelyVisit(node.target); |
| 656 _safelyVisit(node.index); |
| 657 } |
| 658 |
| 659 @override |
| 660 visitInstanceCreationExpression(InstanceCreationExpression node) { |
| 661 // |
| 662 // constructorName |
| 663 // |
| 664 var constructorName = node.constructorName; |
| 665 var constructorElement = |
| 666 resolutionMap.staticElementForConstructorReference(constructorName); |
| 667 if (constructorElement != null) { |
| 668 // anchor- ref/call |
| 669 _handleRefCallEdge(constructorElement, |
| 670 syntacticEntity: constructorName, enclosingTarget: _enclosingVName); |
| 671 |
| 672 // Now write out a ref edge from the same anchor (constructorName) to the |
| 673 // enclosing class of the called constructor, this will make the |
| 674 // invocation of a constructor discoverable when someone inquires about |
| 675 // references to the class. |
| 676 // |
| 677 // We can't call _handleRefEdge as the anchor node has already been |
| 678 // written out. |
| 679 var enclosingEltVName = _vNameFromElement( |
| 680 constructorElement.enclosingElement, schema.RECORD_KIND); |
| 681 var anchorVName = |
| 682 _vNameAnchor(constructorName.offset, constructorName.end); |
| 683 addEdge(anchorVName, schema.REF_EDGE, enclosingEltVName); |
| 684 |
| 685 // TODO(jwren): investigate |
| 686 // assert (element.enclosingElement != null); |
| 687 } |
| 688 // visit children |
| 689 _safelyVisitList(constructorName.type.typeArguments?.arguments); |
| 690 _safelyVisit(node.argumentList); |
| 691 } |
| 692 |
| 693 @override |
| 694 visitMethodDeclaration(MethodDeclaration node) { |
| 695 return _withEnclosingElement(node.element, () { |
| 696 // function node |
| 697 var methodVName = addNodeAndFacts(schema.FUNCTION_KIND, |
| 698 element: node.element, completeFact: schema.DEFINITION); |
| 699 |
| 700 // anchor- defines/binding |
| 701 addAnchorEdgesContainingEdge( |
| 702 syntacticEntity: node.name, |
| 703 edges: [ |
| 704 schema.DEFINES_BINDING_EDGE, |
| 705 ], |
| 706 target: methodVName, |
| 707 enclosingTarget: _enclosingClassVName); |
| 708 |
| 709 // anchor- defines |
| 710 addAnchorEdgesContainingEdge( |
| 711 syntacticEntity: node, |
| 712 edges: [ |
| 713 schema.DEFINES_EDGE, |
| 714 ], |
| 715 target: methodVName); |
| 716 |
| 717 // function type |
| 718 addFunctionType(node.element, node.parameters, methodVName, |
| 719 returnNode: node.returnType); |
| 720 |
| 721 // override edges |
| 722 List<ExecutableElement> overriddenList = |
| 723 _inheritanceManager.lookupOverrides(_enclosingClassElement, |
| 724 resolutionMap.elementDeclaredByMethodDeclaration(node).name); |
| 725 for (ExecutableElement overridden in overriddenList) { |
| 726 if (overridden is MultiplyInheritedExecutableElement) { |
| 727 for (ExecutableElement elt in overridden.inheritedElements) { |
| 728 addEdge(methodVName, schema.OVERRIDES_EDGE, |
| 729 _vNameFromElement(elt, schema.FUNCTION_KIND)); |
| 730 } |
| 731 } else { |
| 732 addEdge(methodVName, schema.OVERRIDES_EDGE, |
| 733 _vNameFromElement(overridden, schema.FUNCTION_KIND)); |
| 734 } |
| 735 } |
| 736 |
| 737 // visit children |
| 738 _safelyVisit(node.documentationComment); |
| 739 _safelyVisitList(node.metadata); |
| 740 _safelyVisit(node.returnType); |
| 741 _safelyVisit(node.typeParameters); |
| 742 _safelyVisit(node.parameters); |
| 743 _safelyVisit(node.body); |
| 744 }); |
| 745 } |
| 746 |
| 747 @override |
| 748 visitMethodInvocation(MethodInvocation node) { |
| 749 var element = node.methodName?.bestElement; |
| 750 |
| 751 // anchor- ref/call |
| 752 _handleRefCallEdge(element, syntacticEntity: node.methodName); |
| 753 |
| 754 // visit children |
| 755 _safelyVisit(node.target); |
| 756 _safelyVisit(node.typeArguments); |
| 757 _safelyVisit(node.argumentList); |
| 758 } |
| 759 |
| 760 @override |
| 761 visitSimpleFormalParameter(SimpleFormalParameter node) { |
| 762 // parameter node |
| 763 var paramVName = addNodeAndFacts(schema.VARIABLE_KIND, |
| 764 element: node.element, |
| 765 subKind: schema.LOCAL_PARAMETER_SUBKIND, |
| 766 completeFact: schema.DEFINITION); |
| 767 |
| 768 // The anchor and anchor edges generation are broken into two cases, the |
| 769 // first case is "method(parameter_name) ...", where the the parameter |
| 770 // character range only includes a parameter name. The second case is for |
| 771 // parameter declarations which are prefixed with a type, 'var', or |
| 772 // 'dynamic', as in "method(var parameter_name) ...". |
| 773 // |
| 774 // With the first case a single anchor range is created, for the second |
| 775 // case an anchor is created on parameter_name, as well as the range |
| 776 // including any prefixes. |
| 777 if (node.offset == node.identifier.offset && |
| 778 node.length == node.identifier.length) { |
| 779 // anchor- defines/binding, defines |
| 780 addAnchorEdgesContainingEdge( |
| 781 syntacticEntity: node.identifier, |
| 782 edges: [ |
| 783 schema.DEFINES_BINDING_EDGE, |
| 784 schema.DEFINES_EDGE, |
| 785 ], |
| 786 target: paramVName, |
| 787 enclosingTarget: _enclosingVName); |
| 788 } else { |
| 789 // anchor- defines/binding |
| 790 addAnchorEdgesContainingEdge( |
| 791 syntacticEntity: node.identifier, |
| 792 edges: [ |
| 793 schema.DEFINES_BINDING_EDGE, |
| 794 ], |
| 795 target: paramVName, |
| 796 enclosingTarget: _enclosingVName); |
| 797 |
| 798 // anchor- defines |
| 799 addAnchorEdgesContainingEdge( |
| 800 syntacticEntity: node, |
| 801 edges: [ |
| 802 schema.DEFINES_EDGE, |
| 803 ], |
| 804 target: paramVName); |
| 805 } |
| 806 |
| 807 // type |
| 808 addEdge( |
| 809 paramVName, |
| 810 schema.TYPED_EDGE, |
| 811 _vNameFromType( |
| 812 resolutionMap.elementDeclaredByFormalParameter(node).type)); |
| 813 |
| 814 // visit children |
| 815 _safelyVisit(node.documentationComment); |
| 816 _safelyVisitList(node.metadata); |
| 817 _safelyVisit(node.type); |
| 818 } |
| 819 |
| 820 @override |
| 821 visitSimpleIdentifier(SimpleIdentifier node) { |
| 822 // Most simple identifiers are "ref" edges. In cases some cases, there may |
| 823 // be other ref/* edges. |
| 824 |
| 825 if (node.getAncestor((node) => node is CommentReference) != null) { |
| 826 // The identifier is in a comment, add just the "ref" edge. |
| 827 _handleRefEdge( |
| 828 node.bestElement, |
| 829 const <String>[schema.REF_EDGE], |
| 830 syntacticEntity: node, |
| 831 ); |
| 832 } else if (node.inDeclarationContext()) { |
| 833 // The node is in a declaration context, and should have |
| 834 // "ref/defines/binding" edge as well as the default "ref" edge. |
| 835 _handleRefEdge( |
| 836 node.bestElement, |
| 837 const <String>[schema.DEFINES_BINDING_EDGE, schema.REF_EDGE], |
| 838 syntacticEntity: node, |
| 839 ); |
| 840 } else { |
| 841 _handleRefCallEdge(node.bestElement, syntacticEntity: node); |
| 842 } |
| 843 |
| 844 // no children to visit |
| 845 } |
| 846 |
| 847 @override |
| 848 visitSuperExpression(SuperExpression node) { |
| 849 _handleThisOrSuper(node); |
| 850 } |
| 851 |
| 852 @override |
| 853 visitThisExpression(ThisExpression node) { |
| 854 _handleThisOrSuper(node); |
| 855 } |
| 856 |
| 857 @override |
| 858 visitUriBasedDirective(UriBasedDirective node) { |
| 859 _handleUriReference(node.uri, node.uriElement); |
| 860 |
| 861 // visit children |
| 862 super.visitUriBasedDirective(node); |
| 863 } |
| 864 |
| 865 @override |
| 866 visitVariableDeclaration(VariableDeclaration node) { |
| 867 // level variable |
| 868 var isLocal = _enclosingVName != _enclosingClassVName && |
| 869 _enclosingVName != _enclosingFileVName; |
| 870 |
| 871 // variable |
| 872 var variableVName = addNodeAndFacts(schema.VARIABLE_KIND, |
| 873 element: node.element, |
| 874 subKind: isLocal ? schema.LOCAL_SUBKIND : schema.FIELD_SUBKIND, |
| 875 completeFact: schema.DEFINITION); |
| 876 |
| 877 // anchor |
| 878 addAnchorEdgesContainingEdge( |
| 879 syntacticEntity: node.name, |
| 880 edges: [ |
| 881 schema.DEFINES_BINDING_EDGE, |
| 882 ], |
| 883 target: variableVName, |
| 884 enclosingTarget: _enclosingVName); |
| 885 |
| 886 // type |
| 887 addEdge( |
| 888 variableVName, |
| 889 schema.TYPED_EDGE, |
| 890 _vNameFromType( |
| 891 resolutionMap.elementDeclaredByVariableDeclaration(node).type)); |
| 892 |
| 893 // visit children |
| 894 _safelyVisit(node.initializer); |
| 895 } |
| 896 |
| 897 Element _findNonSyntheticElement(Element element) { |
| 898 if (element == null || !element.isSynthetic) { |
| 899 return element; |
| 900 } |
| 901 if (element is PropertyAccessorElement) { |
| 902 if (!element.variable.isSynthetic) { |
| 903 return element.variable; |
| 904 } else if (element.correspondingGetter != null && |
| 905 !element.correspondingGetter.isSynthetic) { |
| 906 return element.correspondingGetter; |
| 907 } else if (element.correspondingSetter != null && |
| 908 !element.correspondingSetter.isSynthetic) { |
| 909 return element.correspondingSetter; |
| 910 } |
| 911 } |
| 912 return null; |
| 913 } |
| 914 |
| 915 String _getNodeKind(Element e) { |
| 916 if (e is FieldElement && e.isEnumConstant) { |
| 917 // FieldElement is a kind of VariableElement, so this test case must be |
| 918 // before the e is VariableElement check. |
| 919 return schema.CONSTANT_KIND; |
| 920 } else if (e is VariableElement || e is PrefixElement) { |
| 921 return schema.VARIABLE_KIND; |
| 922 } else if (e is ExecutableElement) { |
| 923 return schema.FUNCTION_KIND; |
| 924 } else if (e is ClassElement || e is TypeParameterElement) { |
| 925 // TODO(jwren): this should be using absvar instead, see |
| 926 // https://kythe.io/docs/schema/#absvar |
| 927 return schema.RECORD_KIND; |
| 928 } |
| 929 return null; |
| 930 } |
| 931 |
| 932 _handleRefCallEdge( |
| 933 Element element, { |
| 934 SyntacticEntity syntacticEntity: null, |
| 935 start: _notFound, |
| 936 end: _notFound, |
| 937 pb_VName enclosingTarget: null, |
| 938 }) { |
| 939 if (element is ExecutableElement && |
| 940 _enclosingVName != _enclosingFileVName) { |
| 941 _handleRefEdge( |
| 942 element, |
| 943 const <String>[schema.REF_CALL_EDGE, schema.REF_EDGE], |
| 944 syntacticEntity: syntacticEntity, |
| 945 start: start, |
| 946 end: end, |
| 947 enclosingTarget: enclosingTarget, |
| 948 enclosingAnchor: _enclosingVName, |
| 949 ); |
| 950 } else { |
| 951 _handleRefEdge( |
| 952 element, |
| 953 const <String>[schema.REF_EDGE], |
| 954 syntacticEntity: syntacticEntity, |
| 955 start: start, |
| 956 end: end, |
| 957 enclosingTarget: enclosingTarget, |
| 958 ); |
| 959 } |
| 960 } |
| 961 |
| 962 /// This is a convenience method for adding ref edges. If the [start] and |
| 963 /// [end] offsets are provided, they are used, otherwise the offsets are |
| 964 /// computed by using the [syntacticEntity]. |
| 965 /// The list of edges is assumed to be non-empty, and are added from the |
| 966 /// anchor to the target generated using the passed [Element]. |
| 967 /// The created [pb_VName] is returned, if not `null` is returned. |
| 968 pb_VName _handleRefEdge( |
| 969 Element element, |
| 970 List<String> refEdgeTypes, { |
| 971 SyntacticEntity syntacticEntity: null, |
| 972 start: _notFound, |
| 973 end: _notFound, |
| 974 pb_VName enclosingTarget: null, |
| 975 pb_VName enclosingAnchor: null, |
| 976 }) { |
| 977 assert(refEdgeTypes.isNotEmpty); |
| 978 element = _findNonSyntheticElement(element); |
| 979 if (element == null) { |
| 980 return null; |
| 981 } |
| 982 |
| 983 // vname |
| 984 var nodeKind = _getNodeKind(element); |
| 985 if (nodeKind == null || nodeKind.isEmpty) { |
| 986 return null; |
| 987 } |
| 988 var vName = _vNameFromElement(element, nodeKind); |
| 989 assert(vName != null); |
| 990 |
| 991 // anchor |
| 992 addAnchorEdgesContainingEdge( |
| 993 start: start, |
| 994 end: end, |
| 995 syntacticEntity: syntacticEntity, |
| 996 edges: refEdgeTypes, |
| 997 target: vName, |
| 998 enclosingTarget: enclosingTarget, |
| 999 enclosingAnchor: enclosingAnchor, |
| 1000 ); |
| 1001 |
| 1002 return vName; |
| 1003 } |
| 1004 |
| 1005 void _handleThisOrSuper(Expression thisOrSuperNode) { |
| 1006 DartType type = thisOrSuperNode.staticType; |
| 1007 if (type != null && type.element != null) { |
| 1008 // Expected SuperExpression.staticType to return the type of the |
| 1009 // supertype, but it returns the type of the enclosing class (same as |
| 1010 // ThisExpression), do some additional work to correct assumption: |
| 1011 if (thisOrSuperNode is SuperExpression && type.element is ClassElement) { |
| 1012 DartType supertype = (type.element as ClassElement).supertype; |
| 1013 if (supertype != null) { |
| 1014 type = supertype; |
| 1015 } |
| 1016 } |
| 1017 // vname |
| 1018 var vName = _vNameFromElement(type.element, schema.RECORD_KIND); |
| 1019 |
| 1020 // anchor |
| 1021 var anchorVName = addAnchorEdgesContainingEdge( |
| 1022 syntacticEntity: thisOrSuperNode, |
| 1023 edges: [schema.REF_EDGE], |
| 1024 target: vName); |
| 1025 |
| 1026 // childof from the anchor |
| 1027 addEdge(anchorVName, schema.CHILD_OF_EDGE, _enclosingVName); |
| 1028 } |
| 1029 |
| 1030 // no children to visit |
| 1031 } |
| 1032 |
| 1033 /// Add a "ref/imports" edge from the passed [uriNode] location to the |
| 1034 /// [referencedElement] [Element]. If the passed element is null, the edge is |
| 1035 /// not written out. |
| 1036 void _handleUriReference(StringLiteral uriNode, Element referencedElement) { |
| 1037 if (referencedElement != null) { |
| 1038 var start = uriNode.offset; |
| 1039 var end = uriNode.end; |
| 1040 |
| 1041 // The following is the expected and common case. |
| 1042 // The contents between the quotes is used as the location to work well |
| 1043 // with CodeSearch. |
| 1044 if (uriNode is SimpleStringLiteral) { |
| 1045 start = uriNode.contentsOffset; |
| 1046 end = uriNode.contentsEnd; |
| 1047 } |
| 1048 |
| 1049 // package node |
| 1050 var packageVName = |
| 1051 _vNameFromElement(referencedElement, schema.PACKAGE_KIND); |
| 1052 |
| 1053 // anchor |
| 1054 addAnchorEdgesContainingEdge( |
| 1055 start: start, |
| 1056 end: end, |
| 1057 edges: [schema.REF_IMPORTS_EDGE], |
| 1058 target: packageVName, |
| 1059 enclosingTarget: _enclosingFileVName); |
| 1060 } |
| 1061 } |
| 1062 |
| 1063 _handleVariableDeclarationListAnnotations( |
| 1064 VariableDeclarationList variableDeclarationList, pb_VName refVName) { |
| 1065 assert(refVName != null); |
| 1066 for (var varDecl in variableDeclarationList.variables) { |
| 1067 if (varDecl.element != null) { |
| 1068 var parentVName = |
| 1069 _vNameFromElement(varDecl.element, schema.VARIABLE_KIND); |
| 1070 addEdge(parentVName, schema.ANNOTATED_BY_EDGE, refVName); |
| 1071 } else { |
| 1072 // The element out of the VarDeclarationList is null |
| 1073 assert(false); |
| 1074 } |
| 1075 } |
| 1076 } |
| 1077 |
| 1078 /// If the given [node] is not `null`, accept this visitor. |
| 1079 void _safelyVisit(AstNode node) { |
| 1080 if (node != null) { |
| 1081 node.accept(this); |
| 1082 } |
| 1083 } |
| 1084 |
| 1085 /// If the given [nodeList] is not `null`, accept this visitor. |
| 1086 void _safelyVisitList(NodeList nodeList) { |
| 1087 if (nodeList != null) { |
| 1088 nodeList.accept(this); |
| 1089 } |
| 1090 } |
| 1091 |
| 1092 _withEnclosingElement(Element element, f()) { |
| 1093 Element outerEnclosingElement = _enclosingElement; |
| 1094 Element outerEnclosingClassElement = _enclosingClassElement; |
| 1095 var outerEnclosingVName = _enclosingVName; |
| 1096 var outerEnclosingClassVName = _enclosingClassVName; |
| 1097 try { |
| 1098 _enclosingElement = element; |
| 1099 if (element is CompilationUnitElement) { |
| 1100 _enclosingFileVName = _enclosingVName = _vNameFile(); |
| 1101 } else if (element is ClassElement) { |
| 1102 _enclosingClassElement = element; |
| 1103 _enclosingClassVName = _enclosingVName = |
| 1104 _vNameFromElement(_enclosingClassElement, schema.RECORD_KIND); |
| 1105 } else if (element is MethodElement || |
| 1106 element is FunctionElement || |
| 1107 element is ConstructorElement) { |
| 1108 _enclosingVName = |
| 1109 _vNameFromElement(_enclosingElement, schema.FUNCTION_KIND); |
| 1110 } |
| 1111 return f(); |
| 1112 } finally { |
| 1113 _enclosingElement = outerEnclosingElement; |
| 1114 _enclosingClassElement = outerEnclosingClassElement; |
| 1115 _enclosingClassVName = outerEnclosingClassVName; |
| 1116 _enclosingVName = outerEnclosingVName; |
| 1117 } |
| 1118 } |
| 1119 } |
| 1120 |
| 1121 /// This class is meant to be a mixin to concrete visitor methods to walk the |
| 1122 /// [Element] or [AstNode]s produced by the Dart Analyzer to output Kythe |
| 1123 /// [pb_Entry] protos. |
| 1124 abstract class OutputUtils { |
| 1125 /// A set of [String]s which have already had a name [pb_VName] created. |
| 1126 final Set<String> nameNodes = new Set<String>(); |
| 1127 String get corpus; |
| 1128 pb_VName get dynamicBuiltin => _vName(schema.DYNAMIC_KIND, '', '', ''); |
| 1129 |
| 1130 String get enclosingFilePath; |
| 1131 |
| 1132 List<pb_Entry> get entries; |
| 1133 pb_VName get fnBuiltin => _vName(schema.FN_BUILTIN, '', '', ''); |
| 1134 pb_VName get voidBuiltin => _vName(schema.VOID_BUILTIN, '', '', ''); |
| 1135 |
| 1136 /// This is a convenience method for adding anchors. If the [start] and [end] |
| 1137 /// offsets are provided, they are used, otherwise the offsets are computed by |
| 1138 /// using the [syntacticEntity]. If a non-empty list of edges is provided, as |
| 1139 /// well as a target, then this method also adds the edges from the anchor to |
| 1140 /// target. The anchor [pb_VName] is returned. |
| 1141 /// |
| 1142 /// If a [target] and [enclosingTarget] are provided, a childof edge is |
| 1143 /// written out from the target to the enclosing target. |
| 1144 /// |
| 1145 /// If an [enclosingAnchor] is provided a childof edge is written out from the |
| 1146 /// anchor to the enclosing anchor. In cases where ref/call is an edge, this |
| 1147 /// is required to generate the callgraph. |
| 1148 /// |
| 1149 /// Finally, for all anchors, a childof edge with a target of the enclosing |
| 1150 /// file is written out. |
| 1151 pb_VName addAnchorEdgesContainingEdge({ |
| 1152 SyntacticEntity syntacticEntity: null, |
| 1153 int start: _notFound, |
| 1154 int end: _notFound, |
| 1155 List<String> edges: const [], |
| 1156 pb_VName target: null, |
| 1157 pb_VName enclosingTarget: null, |
| 1158 pb_VName enclosingAnchor: null, |
| 1159 }) { |
| 1160 if (start == _notFound && end == _notFound) { |
| 1161 if (syntacticEntity != null) { |
| 1162 start = syntacticEntity.offset; |
| 1163 end = syntacticEntity.end; |
| 1164 } else { |
| 1165 throw new Exception('Offset positions were not provided when calling ' |
| 1166 'addAnchorEdgesContainingEdge'); |
| 1167 } |
| 1168 } |
| 1169 // TODO(jwren) investigate |
| 1170 // assert(start < end); |
| 1171 var anchorVName = _vNameAnchor(start, end); |
| 1172 addFact(anchorVName, schema.NODE_KIND_FACT, _encode(schema.ANCHOR_KIND)); |
| 1173 addFact(anchorVName, schema.ANCHOR_START_FACT, _encodeInt(start)); |
| 1174 addFact(anchorVName, schema.ANCHOR_END_FACT, _encodeInt(end)); |
| 1175 if (target != null) { |
| 1176 for (String edge in edges) { |
| 1177 addEdge(anchorVName, edge, target); |
| 1178 } |
| 1179 if (enclosingTarget != null) { |
| 1180 addEdge(target, schema.CHILD_OF_EDGE, enclosingTarget); |
| 1181 } |
| 1182 } |
| 1183 // If provided, write out the childof edge to the enclosing anchor |
| 1184 if (enclosingAnchor != null) { |
| 1185 addEdge(anchorVName, schema.CHILD_OF_EDGE, enclosingAnchor); |
| 1186 } |
| 1187 |
| 1188 // Assert that if ref/call is one of the edges, that and enclosing anchor |
| 1189 // was provided for the callgraph. |
| 1190 // Documentation at http://kythe.io/docs/schema/callgraph.html |
| 1191 if (edges.contains(schema.REF_CALL_EDGE)) { |
| 1192 assert(enclosingAnchor != null); |
| 1193 } |
| 1194 |
| 1195 // Finally add the childof edge to the enclosing file VName. |
| 1196 addEdge(anchorVName, schema.CHILD_OF_EDGE, _vNameFile()); |
| 1197 return anchorVName; |
| 1198 } |
| 1199 |
| 1200 /// TODO(jwren): for cases where the target is a name, we need the same kind |
| 1201 /// of logic as [addNameFact] to prevent the edge from being written out. |
| 1202 /// This is a convenience method for visitors to add an edge Entry. |
| 1203 pb_Entry addEdge(pb_VName source, String edgeKind, pb_VName target, |
| 1204 {int ordinalIntValue: _notFound}) { |
| 1205 if (ordinalIntValue == _notFound) { |
| 1206 return addEntry(source, edgeKind, target, "/", new List<int>()); |
| 1207 } else { |
| 1208 return addEntry(source, edgeKind, target, schema.ORDINAL, |
| 1209 _encodeInt(ordinalIntValue)); |
| 1210 } |
| 1211 } |
| 1212 |
| 1213 pb_Entry addEntry(pb_VName source, String edgeKind, pb_VName target, |
| 1214 String factName, List<int> factValue) { |
| 1215 assert(source != null); |
| 1216 assert(factName != null); |
| 1217 assert(factValue != null); |
| 1218 // factValue may be an empty array, the fact may be that a file text or |
| 1219 // document text is empty |
| 1220 var entry = pb_Entry.create() |
| 1221 ..source = source |
| 1222 ..factName = factName |
| 1223 ..factValue = factValue; |
| 1224 if (edgeKind != null && edgeKind.isNotEmpty) { |
| 1225 entry.edgeKind = edgeKind; |
| 1226 entry.target = target; |
| 1227 } |
| 1228 entries.add(entry); |
| 1229 return entry; |
| 1230 } |
| 1231 |
| 1232 /// This is a convenience method for visitors to add a fact [pb_Entry]. |
| 1233 pb_Entry addFact(pb_VName source, String factName, List<int> factValue) { |
| 1234 return addEntry(source, null, null, factName, factValue); |
| 1235 } |
| 1236 |
| 1237 /// This is a convenience method for adding function types. |
| 1238 pb_VName addFunctionType( |
| 1239 Element functionElement, |
| 1240 FormalParameterList paramNodes, |
| 1241 pb_VName functionVName, { |
| 1242 AstNode returnNode: null, |
| 1243 }) { |
| 1244 var i = 0; |
| 1245 var funcTypeVName = |
| 1246 addNodeAndFacts(schema.TAPP_KIND, element: functionElement); |
| 1247 addEdge(funcTypeVName, schema.PARAM_EDGE, fnBuiltin, ordinalIntValue: i++); |
| 1248 |
| 1249 var returnTypeVName; |
| 1250 if (returnNode is TypeName) { |
| 1251 // MethodDeclaration and FunctionDeclaration both return a TypeName from |
| 1252 // returnType |
| 1253 if (resolutionMap.typeForTypeName(returnNode).isVoid) { |
| 1254 returnTypeVName = voidBuiltin; |
| 1255 } else { |
| 1256 returnTypeVName = |
| 1257 _vNameFromElement(returnNode.name.bestElement, schema.TAPP_KIND); |
| 1258 } |
| 1259 } else if (returnNode is Identifier) { |
| 1260 // ConstructorDeclaration returns an Identifier from returnType |
| 1261 if (resolutionMap.bestTypeForExpression(returnNode).isVoid) { |
| 1262 returnTypeVName = voidBuiltin; |
| 1263 } else { |
| 1264 returnTypeVName = |
| 1265 _vNameFromElement(returnNode.bestElement, schema.TAPP_KIND); |
| 1266 } |
| 1267 } |
| 1268 // else: return type is null, void, unresolved. |
| 1269 |
| 1270 if (returnTypeVName != null) { |
| 1271 addEdge(funcTypeVName, schema.PARAM_EDGE, returnTypeVName, |
| 1272 ordinalIntValue: i++); |
| 1273 } |
| 1274 |
| 1275 if (paramNodes != null) { |
| 1276 for (FormalParameter paramNode in paramNodes.parameters) { |
| 1277 var paramTypeVName = dynamicBuiltin; |
| 1278 if (!resolutionMap |
| 1279 .elementDeclaredByFormalParameter(paramNode) |
| 1280 .type |
| 1281 .isDynamic) { |
| 1282 paramTypeVName = _vNameFromElement( |
| 1283 resolutionMap |
| 1284 .elementDeclaredByFormalParameter(paramNode) |
| 1285 .type |
| 1286 .element, |
| 1287 schema.TAPP_KIND); |
| 1288 } |
| 1289 addEdge(funcTypeVName, schema.PARAM_EDGE, paramTypeVName, |
| 1290 ordinalIntValue: i++); |
| 1291 } |
| 1292 } |
| 1293 addEdge(functionVName, schema.TYPED_EDGE, funcTypeVName); |
| 1294 return funcTypeVName; |
| 1295 } |
| 1296 |
| 1297 /// This is a convenience method for adding nodes with facts. |
| 1298 /// If an [pb_VName] is passed, it is used, otherwise an element is required |
| 1299 /// which is used to create a [pb_VName]. Either [nodeVName] must be non-null
or |
| 1300 /// [element] must be non-null. Other optional parameters if passed are then |
| 1301 /// used to set the associated facts on the [pb_VName]. This method does not |
| 1302 /// currently guarantee that the inputs to these fact kinds are valid for the |
| 1303 /// associated nodeKind- if a non-null, then it will set. |
| 1304 pb_VName addNodeAndFacts(String nodeKind, |
| 1305 {Element element: null, |
| 1306 pb_VName nodeVName: null, |
| 1307 String subKind: null, |
| 1308 String completeFact: null}) { |
| 1309 if (nodeVName == null) { |
| 1310 nodeVName = _vNameFromElement(element, nodeKind); |
| 1311 } |
| 1312 addFact(nodeVName, schema.NODE_KIND_FACT, _encode(nodeKind)); |
| 1313 if (subKind != null) { |
| 1314 addFact(nodeVName, schema.SUBKIND_FACT, _encode(subKind)); |
| 1315 } |
| 1316 if (completeFact != null) { |
| 1317 addFact(nodeVName, schema.COMPLETE_FACT, _encode(completeFact)); |
| 1318 } |
| 1319 return nodeVName; |
| 1320 } |
| 1321 |
| 1322 List<int> _encode(String str) { |
| 1323 return UTF8.encode(str); |
| 1324 } |
| 1325 |
| 1326 List<int> _encodeInt(int i) { |
| 1327 return UTF8.encode(i.toString()); |
| 1328 } |
| 1329 |
| 1330 /// Given all parameters for a [pb_VName] this method creates and returns a |
| 1331 /// [pb_VName]. |
| 1332 pb_VName _vName(String signature, String corpus, String root, String path, |
| 1333 [String language = schema.DART_LANG]) { |
| 1334 return pb_VName.create() |
| 1335 ..signature = signature |
| 1336 ..corpus = corpus |
| 1337 ..root = root |
| 1338 ..path = path |
| 1339 ..language = language; |
| 1340 } |
| 1341 |
| 1342 /// Returns an anchor [pb_VName] corresponding to the given start and end |
| 1343 /// offsets. |
| 1344 pb_VName _vNameAnchor(int start, int end) { |
| 1345 return _vName( |
| 1346 _getAnchorSignature(start, end), corpus, '', enclosingFilePath); |
| 1347 } |
| 1348 |
| 1349 /// Return the [pb_VName] for this file. |
| 1350 pb_VName _vNameFile() { |
| 1351 // file vnames, the signature and language are not set |
| 1352 return _vName('', corpus, '', enclosingFilePath, ''); |
| 1353 } |
| 1354 |
| 1355 /// Given some [Element] and Kythe node kind, this method generates and |
| 1356 /// returns the [pb_VName]. |
| 1357 pb_VName _vNameFromElement(Element e, String nodeKind) { |
| 1358 assert(nodeKind != schema.FILE_KIND); |
| 1359 // general case |
| 1360 return _vName(_getSignature(e, nodeKind, corpus), corpus, '', _getPath(e)); |
| 1361 } |
| 1362 |
| 1363 /// Returns a [pb_VName] corresponding to the given [DartType]. |
| 1364 pb_VName _vNameFromType(DartType type) { |
| 1365 if (type == null || type.isDynamic) { |
| 1366 return dynamicBuiltin; |
| 1367 } else if (type.isVoid) { |
| 1368 return voidBuiltin; |
| 1369 } else if (type.element is ClassElement) { |
| 1370 return _vNameFromElement(type.element, schema.RECORD_KIND); |
| 1371 } else { |
| 1372 return dynamicBuiltin; |
| 1373 } |
| 1374 } |
| 1375 } |
| 1376 |
| 1377 class CodedBufferWriter { |
| 1378 CodedBufferWriter(var v); |
| 1379 toBuffer() {} |
| 1380 } |
| 1381 |
| 1382 class pb_Entry { |
| 1383 var source, edgeKind, target, factName, factValue; |
| 1384 static pb_Entry create() => new pb_Entry(); |
| 1385 writeToBuffer() {} |
| 1386 } |
| 1387 |
| 1388 class pb_VName { |
| 1389 var signature, corpus, root, path, language; |
| 1390 static pb_VName create() => new pb_VName(); |
| 1391 } |
| 1392 |
| 1393 /// This visitor class should be used by [_getSignature]. |
| 1394 /// |
| 1395 /// This visitor is an [GeneralizingElementVisitor] which builds up a [String] |
| 1396 /// signature for a given [Element], uniqueness is guaranteed within the |
| 1397 /// enclosing file. |
| 1398 class SignatureElementVisitor extends GeneralizingElementVisitor<StringBuffer> { |
| 1399 static SignatureElementVisitor instance = new SignatureElementVisitor(); |
| 1400 |
| 1401 @override |
| 1402 StringBuffer visitCompilationUnitElement(CompilationUnitElement e) { |
| 1403 return new StringBuffer(); |
| 1404 } |
| 1405 |
| 1406 @override |
| 1407 StringBuffer visitElement(Element e) { |
| 1408 assert(e is! MultiplyInheritedExecutableElement); |
| 1409 var enclosingElt = e.enclosingElement; |
| 1410 var buffer = enclosingElt.accept(this); |
| 1411 if (buffer.isNotEmpty) { |
| 1412 buffer.write('#'); |
| 1413 } |
| 1414 if (e is MethodElement && e.name == '-' && e.parameters.length == 1) { |
| 1415 buffer.write('unary-'); |
| 1416 } else if (e is ConstructorElement) { |
| 1417 buffer.write(_computeConstructorElementName(e)); |
| 1418 } else { |
| 1419 buffer.write(e.name); |
| 1420 } |
| 1421 if (enclosingElt is ExecutableElement) { |
| 1422 buffer..write('@')..write(e.nameOffset - enclosingElt.nameOffset); |
| 1423 } |
| 1424 return buffer; |
| 1425 } |
| 1426 |
| 1427 @override |
| 1428 StringBuffer visitLibraryElement(LibraryElement e) { |
| 1429 return new StringBuffer('library:${e.displayName}'); |
| 1430 } |
| 1431 |
| 1432 @override |
| 1433 StringBuffer visitTypeParameterElement(TypeParameterElement e) { |
| 1434 // It is legal to have a named constructor with the same name as a type |
| 1435 // parameter. So we distinguish them by using '.' between the class (or |
| 1436 // typedef) name and the type parameter name. |
| 1437 return e.enclosingElement.accept(this)..write('.')..write(e.name); |
| 1438 } |
| 1439 } |
| OLD | NEW |