| <!-- |
| Copyright 2016 The LUCI Authors. All rights reserved. |
| Use of this source code is governed under the Apache License, Version 2.0 |
| that can be found in the LICENSE file. |
| --> |
| |
| <!-- |
| rpcExplorer.descUtil exposes helper methods to work with protobuf |
| descriptor messages. |
| Primarily it implements name and comments resolution in a FileDescriptorSet. |
| --> |
| <script> |
| 'use strict'; |
| |
| var rpcExplorer = (function(rpcExplorer) { |
| |
| rpcExplorer.descUtil = { |
| |
| /** |
| * For all descriptors in the file annotate resolves |
| * SourceLocationInfo.Location message and assigns it to the |
| * sourceCodeInfo property of the descriptor. |
| * If fileSet is provided, for all field descriptors with non-empty type |
| * name, resolves the referenced type and assigns to "rpcExpTypeInfo" |
| * of the field descriptor. |
| * Prerequisite reading: |
| * https://github.com/luci/luci-go/blob/ea240d0/common/proto/google/descriptor/descriptor.proto#L713 |
| * @param {FileDescriptorProto} file |
| * @param {FileDescriptorSet} fileSet |
| */ |
| annotate: function(file, fileSet) { |
| var self = this; |
| if (!file.sourceCodeInfo) { |
| return; |
| } |
| |
| // First, build a map { path -> location message }. |
| function key(path) { |
| var key = ''; |
| for (var i = 0; i < path.length; i++) { |
| key += path[i] + '.'; |
| } |
| return key; |
| } |
| var locationMap = {}; |
| for (var i = 0; i < file.sourceCodeInfo.location.length; i++) { |
| var loc = file.sourceCodeInfo.location[i]; |
| if (loc.path) { |
| locationMap[key(loc.path)] = loc; |
| } |
| } |
| |
| // Now join all descriptors in file with the map. |
| var path = []; |
| |
| function annotateList(list, field, fn) { |
| if (!list) { |
| return; |
| } |
| path.push(field, 0); |
| for (var i = 0; i < list.length; i++) { |
| path[path.length - 1] = i; |
| list[i].sourceCodeInfo = locationMap[key(path)]; |
| if (fn) { |
| fn(list[i]); |
| } |
| } |
| path.pop(); |
| path.pop(); |
| } |
| |
| // The magic numbers below are message field numbers defined in |
| // descriptor.proto. |
| |
| function annotateMessage(msg) { |
| annotateList(msg.field, 2); |
| annotateList(msg.nestedType, 3, annotateMessage); |
| annotateList(msg.enumType, 4, annotateEnum); |
| |
| // Resolve referenced types. |
| if (!fileSet || !msg.field) { |
| return; |
| } |
| for (var i = 0; i < msg.field.length; i++) { |
| var field = msg.field[i]; |
| if (field.typeName) { |
| field.rpcExpTypeInfo = self.resolve( |
| fileSet, self.trimPrefixDot(field.typeName)); |
| } |
| } |
| } |
| |
| function annotateEnum(e) { |
| annotateList(e.value, 2); |
| } |
| |
| function annotateService(svc) { |
| annotateList(svc.method, 2); |
| } |
| |
| annotateList(file.messageType, 4, annotateMessage); |
| annotateList(file.enumType, 5, annotateEnum); |
| annotateList(file.service, 6, annotateService); |
| }, |
| |
| /** |
| * Annotates a FileDescriptorSet. |
| */ |
| annotateSet: function(fileSet) { |
| for (var i = 0; i < fileSet.file.length; i++) { |
| this.annotate(fileSet.file[i], fileSet); |
| } |
| }, |
| |
| splitFullName: function(fullName) { |
| var lastDot = fullName.lastIndexOf('.'); |
| if (lastDot === -1) { |
| return { |
| pkg: '', |
| name: fullName |
| }; |
| } |
| |
| return { |
| pkg: fullName.substr(0, lastDot), |
| name: fullName.substr(lastDot + 1) |
| }; |
| }, |
| |
| /** |
| * Resolves services, methods, messages, fields, enums and enum values. |
| */ |
| resolve: function(desc, name) { |
| if (!desc || !name) { |
| return null; |
| } |
| name = this.splitFullName(name); |
| |
| var self = this; |
| |
| // searches in each list. |
| function checkLists(lists) { |
| if (!lists) { |
| return null; |
| } |
| for (var type in lists) { |
| var desc = self.findByName(lists[type], name.name); |
| if (desc) { |
| return {type: type, desc: desc }; |
| } |
| } |
| return null; |
| } |
| |
| // Check top-level descriptors. |
| for (var i = 0; i < desc.file.length; i++) { |
| var file = desc.file[i]; |
| if (file['package'] != name.pkg) { |
| continue |
| } |
| |
| var result = checkLists({ |
| service: file.service, |
| messageType: file.messageType, |
| enumType: file.enumType |
| }); |
| if (result) { |
| return result; |
| } |
| } |
| |
| // Possibly the entity we are resolving is not top-level and |
| // name.name references an object inside an object referenced by |
| // name.pkg. Try to resolve name.pkg. |
| var parent = this.resolve(desc, name.pkg); |
| if (!parent) { |
| return null; |
| } |
| switch (parent.type) { |
| case 'service': |
| return checkLists({ method: parent.desc.method }); |
| |
| case 'messageType': |
| return checkLists({ |
| field: parent.desc.field, |
| messageType: parent.desc.nestedType, |
| enumType: parent.desc.enumType |
| }); |
| |
| case 'enumType': |
| return checkLists({ |
| enumValue: parent.desc.value |
| }); |
| |
| default: |
| return null; |
| } |
| }, |
| |
| findByName: function(array, name) { |
| if (!array) { |
| return null; |
| } |
| for (var i = 0; i < array.length; i++) { |
| if (array[i].name === name) { |
| return array[i]; |
| } |
| } |
| return null; |
| }, |
| |
| findByJsonName: function(array, jsonName) { |
| if (!array) { |
| return null; |
| } |
| for (var i = 0; i < array.length; i++) { |
| if (array[i].jsonName === jsonName) { |
| return array[i]; |
| } |
| } |
| return null; |
| }, |
| |
| trimPrefixDot: function(name) { |
| if (typeof name === 'string' && name.charAt(0) === '.') { |
| name = name.substr(1); |
| } |
| return name; |
| }, |
| |
| normalizeComment: function(comment, titleToStrip) { |
| if (!comment) { |
| return ''; |
| } |
| |
| // Skip any leading whitespace. |
| var i = 0; |
| while (i < comment.length && comment[i] == ' ') { |
| i++; |
| } |
| comment = comment.substr(i); |
| |
| // Keep first paragraph only. A paragraph is delimited by '\n\n' or |
| // '. '. |
| var idx = comment.indexOf('\n\n'); |
| if (idx == -1) { |
| idx = comment.indexOf('. ') |
| } |
| if (idx != -1) { |
| comment = comment.substr(0, idx); |
| } |
| |
| // Strip leading title. |
| if (titleToStrip) { |
| var prefix = titleToStrip + ' '; |
| if (comment.substr(0, prefix.length) == prefix) { |
| comment = comment.substr(prefix.length); |
| } |
| } |
| |
| return comment; |
| } |
| }; |
| |
| return rpcExplorer; |
| }(rpcExplorer || {})); |
| </script> |