| 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:collection'; |
| 6 |
| 7 import 'package:analysis_server/plugin/protocol/protocol.dart' as server; |
| 8 import 'package:analysis_server/src/channel/channel.dart'; |
| 9 import 'package:analysis_server/src/plugin/result_collector.dart'; |
| 10 import 'package:analysis_server/src/plugin/result_converter.dart'; |
| 11 import 'package:analysis_server/src/plugin/result_merger.dart'; |
| 12 import 'package:analyzer/file_system/file_system.dart'; |
| 13 import 'package:analyzer_plugin/protocol/protocol.dart' as plugin; |
| 14 import 'package:analyzer_plugin/protocol/protocol_constants.dart' as plugin; |
| 15 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin; |
| 16 |
| 17 /** |
| 18 * The object used to coordinate the results of notifications from the analysis |
| 19 * server and multiple plugins. |
| 20 */ |
| 21 class NotificationManager { |
| 22 /** |
| 23 * The identifier used to identify results from the server. |
| 24 */ |
| 25 static const String serverId = 'server'; |
| 26 |
| 27 /** |
| 28 * The channel used to send notifications to the client. |
| 29 */ |
| 30 final ServerCommunicationChannel channel; |
| 31 |
| 32 /** |
| 33 * The resource provider used to get the path context. |
| 34 */ |
| 35 final ResourceProvider provider; |
| 36 |
| 37 /** |
| 38 * A list of the paths of files and directories that are included for analysis
. |
| 39 */ |
| 40 List<String> includedPaths = <String>[]; |
| 41 |
| 42 /** |
| 43 * A list of the paths of files and directories that are excluded from |
| 44 * analysis. |
| 45 */ |
| 46 List<String> excludedPaths = <String>[]; |
| 47 |
| 48 /** |
| 49 * The current set of subscriptions to which the client has subscribed. |
| 50 */ |
| 51 Map<server.AnalysisService, List<String>> currentSubscriptions = |
| 52 <server.AnalysisService, List<String>>{}; |
| 53 |
| 54 /** |
| 55 * The collector being used to collect the analysis errors from the plugins. |
| 56 */ |
| 57 ResultCollector<List<server.AnalysisError>> errors; |
| 58 |
| 59 /** |
| 60 * The collector being used to collect the folding regions from the plugins. |
| 61 */ |
| 62 ResultCollector<List<server.FoldingRegion>> folding; |
| 63 |
| 64 /** |
| 65 * The collector being used to collect the highlight regions from the plugins. |
| 66 */ |
| 67 ResultCollector<List<server.HighlightRegion>> highlights; |
| 68 |
| 69 /** |
| 70 * The collector being used to collect the navigation parameters from the |
| 71 * plugins. |
| 72 */ |
| 73 ResultCollector<server.AnalysisNavigationParams> navigation; |
| 74 |
| 75 /** |
| 76 * The collector being used to collect the occurrences from the plugins. |
| 77 */ |
| 78 ResultCollector<List<server.Occurrences>> occurrences; |
| 79 |
| 80 /** |
| 81 * The collector being used to collect the outlines from the plugins. |
| 82 */ |
| 83 ResultCollector<List<server.Outline>> outlines; |
| 84 |
| 85 /** |
| 86 * The object used to convert results. |
| 87 */ |
| 88 ResultConverter converter = new ResultConverter(); |
| 89 |
| 90 /** |
| 91 * The object used to merge results. |
| 92 */ |
| 93 ResultMerger merger = new ResultMerger(); |
| 94 |
| 95 /** |
| 96 * Initialize a newly created notification manager. |
| 97 */ |
| 98 NotificationManager(this.channel, this.provider) { |
| 99 errors = new ResultCollector<List<server.AnalysisError>>(serverId, |
| 100 predicate: _isIncluded); |
| 101 folding = new ResultCollector<List<server.FoldingRegion>>(serverId); |
| 102 highlights = new ResultCollector<List<server.HighlightRegion>>(serverId); |
| 103 navigation = new ResultCollector<server.AnalysisNavigationParams>(serverId); |
| 104 occurrences = new ResultCollector<List<server.Occurrences>>(serverId); |
| 105 outlines = new ResultCollector<List<server.Outline>>(serverId); |
| 106 } |
| 107 |
| 108 /** |
| 109 * Handle the given [notification] from the plugin with the given [pluginId]. |
| 110 */ |
| 111 void handlePluginNotification( |
| 112 String pluginId, plugin.Notification notification) { |
| 113 String event = notification.event; |
| 114 switch (event) { |
| 115 case plugin.ANALYSIS_NOTIFICATION_ERRORS: |
| 116 plugin.AnalysisErrorsParams params = |
| 117 new plugin.AnalysisErrorsParams.fromNotification(notification); |
| 118 recordAnalysisErrors( |
| 119 pluginId, |
| 120 params.file, |
| 121 params.errors |
| 122 .map((plugin.AnalysisError error) => |
| 123 converter.convertAnalysisError(error)) |
| 124 .toList()); |
| 125 break; |
| 126 case plugin.ANALYSIS_NOTIFICATION_FOLDING: |
| 127 plugin.AnalysisFoldingParams params = |
| 128 new plugin.AnalysisFoldingParams.fromNotification(notification); |
| 129 recordFoldingRegions( |
| 130 pluginId, |
| 131 params.file, |
| 132 params.regions |
| 133 .map((plugin.FoldingRegion region) => |
| 134 converter.convertFoldingRegion(region)) |
| 135 .toList()); |
| 136 break; |
| 137 case plugin.ANALYSIS_NOTIFICATION_HIGHLIGHTS: |
| 138 plugin.AnalysisHighlightsParams params = |
| 139 new plugin.AnalysisHighlightsParams.fromNotification(notification); |
| 140 recordHighlightRegions( |
| 141 pluginId, |
| 142 params.file, |
| 143 params.regions |
| 144 .map((plugin.HighlightRegion region) => |
| 145 converter.convertHighlightRegion(region)) |
| 146 .toList()); |
| 147 break; |
| 148 case plugin.ANALYSIS_NOTIFICATION_NAVIGATION: |
| 149 plugin.AnalysisNavigationParams params = |
| 150 new plugin.AnalysisNavigationParams.fromNotification(notification); |
| 151 recordNavigationParams(pluginId, params.file, |
| 152 converter.convertAnalysisNavigationParams(params)); |
| 153 break; |
| 154 case plugin.ANALYSIS_NOTIFICATION_OCCURRENCES: |
| 155 plugin.AnalysisOccurrencesParams params = |
| 156 new plugin.AnalysisOccurrencesParams.fromNotification(notification); |
| 157 recordOccurrences( |
| 158 pluginId, |
| 159 params.file, |
| 160 params.occurrences |
| 161 .map((plugin.Occurrences occurrences) => |
| 162 converter.convertOccurrences(occurrences)) |
| 163 .toList()); |
| 164 break; |
| 165 case plugin.ANALYSIS_NOTIFICATION_OUTLINE: |
| 166 plugin.AnalysisOutlineParams params = |
| 167 new plugin.AnalysisOutlineParams.fromNotification(notification); |
| 168 recordOutlines( |
| 169 pluginId, |
| 170 params.file, |
| 171 params.outline |
| 172 .map((plugin.Outline outline) => |
| 173 converter.convertOutline(outline)) |
| 174 .toList()); |
| 175 break; |
| 176 } |
| 177 } |
| 178 |
| 179 /** |
| 180 * Record error information from the plugin with the given [pluginId] for the |
| 181 * file with the given [filePath]. |
| 182 */ |
| 183 void recordAnalysisErrors( |
| 184 String pluginId, String filePath, List<server.AnalysisError> errorData) { |
| 185 if (errors.isCollectingFor(filePath)) { |
| 186 errors.putResults(filePath, pluginId, errorData); |
| 187 List<List<server.AnalysisError>> unmergedErrors = |
| 188 errors.getResults(filePath); |
| 189 List<server.AnalysisError> mergedErrors = |
| 190 merger.mergeAnalysisErrors(unmergedErrors); |
| 191 channel.sendNotification( |
| 192 new server.AnalysisErrorsParams(filePath, mergedErrors) |
| 193 .toNotification()); |
| 194 } |
| 195 } |
| 196 |
| 197 /** |
| 198 * Record folding information from the plugin with the given [pluginId] for |
| 199 * the file with the given [filePath]. |
| 200 */ |
| 201 void recordFoldingRegions(String pluginId, String filePath, |
| 202 List<server.FoldingRegion> foldingData) { |
| 203 if (folding.isCollectingFor(filePath)) { |
| 204 folding.putResults(filePath, pluginId, foldingData); |
| 205 List<List<server.FoldingRegion>> unmergedFolding = |
| 206 folding.getResults(filePath); |
| 207 List<server.FoldingRegion> mergedFolding = |
| 208 merger.mergeFoldingRegions(unmergedFolding); |
| 209 channel.sendNotification( |
| 210 new server.AnalysisFoldingParams(filePath, mergedFolding) |
| 211 .toNotification()); |
| 212 } |
| 213 } |
| 214 |
| 215 /** |
| 216 * Record highlight information from the plugin with the given [pluginId] for |
| 217 * the file with the given [filePath]. |
| 218 */ |
| 219 void recordHighlightRegions(String pluginId, String filePath, |
| 220 List<server.HighlightRegion> highlightData) { |
| 221 if (highlights.isCollectingFor(filePath)) { |
| 222 highlights.putResults(filePath, pluginId, highlightData); |
| 223 List<List<server.HighlightRegion>> unmergedHighlights = |
| 224 highlights.getResults(filePath); |
| 225 List<server.HighlightRegion> mergedHighlights = |
| 226 merger.mergeHighlightRegions(unmergedHighlights); |
| 227 channel.sendNotification( |
| 228 new server.AnalysisHighlightsParams(filePath, mergedHighlights) |
| 229 .toNotification()); |
| 230 } |
| 231 } |
| 232 |
| 233 /** |
| 234 * Record navigation information from the plugin with the given [pluginId] for |
| 235 * the file with the given [filePath]. |
| 236 */ |
| 237 void recordNavigationParams(String pluginId, String filePath, |
| 238 server.AnalysisNavigationParams navigationData) { |
| 239 if (navigation.isCollectingFor(filePath)) { |
| 240 navigation.putResults(filePath, pluginId, navigationData); |
| 241 List<server.AnalysisNavigationParams> unmergedNavigations = |
| 242 navigation.getResults(filePath); |
| 243 server.AnalysisNavigationParams mergedNavigations = |
| 244 merger.mergeNavigation(unmergedNavigations); |
| 245 channel.sendNotification(mergedNavigations.toNotification()); |
| 246 } |
| 247 } |
| 248 |
| 249 /** |
| 250 * Record occurrences information from the plugin with the given [pluginId] |
| 251 * for the file with the given [filePath]. |
| 252 */ |
| 253 void recordOccurrences(String pluginId, String filePath, |
| 254 List<server.Occurrences> occurrencesData) { |
| 255 if (occurrences.isCollectingFor(filePath)) { |
| 256 occurrences.putResults(filePath, pluginId, occurrencesData); |
| 257 List<List<server.Occurrences>> unmergedOccurrences = |
| 258 occurrences.getResults(filePath); |
| 259 List<server.Occurrences> mergedOccurrences = |
| 260 merger.mergeOccurrences(unmergedOccurrences); |
| 261 channel.sendNotification( |
| 262 new server.AnalysisOccurrencesParams(filePath, mergedOccurrences) |
| 263 .toNotification()); |
| 264 } |
| 265 } |
| 266 |
| 267 /** |
| 268 * Record outline information from the plugin with the given [pluginId] for |
| 269 * the file with the given [filePath]. |
| 270 */ |
| 271 void recordOutlines( |
| 272 String pluginId, String filePath, List<server.Outline> outlineData) { |
| 273 if (outlines.isCollectingFor(filePath)) { |
| 274 outlines.putResults(filePath, pluginId, outlineData); |
| 275 List<List<server.Outline>> unmergedOutlines = |
| 276 outlines.getResults(filePath); |
| 277 List<server.Outline> mergedOutlines = |
| 278 merger.mergeOutline(unmergedOutlines); |
| 279 channel.sendNotification(new server.AnalysisOutlineParams( |
| 280 filePath, server.FileKind.LIBRARY, mergedOutlines[0]) |
| 281 .toNotification()); |
| 282 } |
| 283 } |
| 284 |
| 285 /** |
| 286 * Set the lists of [included] and [excluded] files. |
| 287 */ |
| 288 void setAnalysisRoots(List<String> included, List<String> excluded) { |
| 289 includedPaths = included; |
| 290 excludedPaths = excluded; |
| 291 } |
| 292 |
| 293 /** |
| 294 * Set the current subscriptions to the given set of [newSubscriptions]. |
| 295 */ |
| 296 void setSubscriptions( |
| 297 Map<server.AnalysisService, List<String>> newSubscriptions) { |
| 298 /** |
| 299 * Return the collector associated with the given service, or `null` if the |
| 300 * service is not handled by this manager. |
| 301 */ |
| 302 ResultCollector collectorFor(server.AnalysisService service) { |
| 303 switch (service) { |
| 304 case server.AnalysisService.FOLDING: |
| 305 return folding; |
| 306 case server.AnalysisService.HIGHLIGHTS: |
| 307 return highlights; |
| 308 case server.AnalysisService.NAVIGATION: |
| 309 return navigation; |
| 310 case server.AnalysisService.OCCURRENCES: |
| 311 return occurrences; |
| 312 case server.AnalysisService.OUTLINE: |
| 313 return outlines; |
| 314 } |
| 315 return null; |
| 316 } |
| 317 |
| 318 Set<server.AnalysisService> services = |
| 319 new HashSet<server.AnalysisService>(); |
| 320 services.addAll(currentSubscriptions.keys); |
| 321 services.addAll(newSubscriptions.keys); |
| 322 services.forEach((server.AnalysisService service) { |
| 323 ResultCollector collector = collectorFor(service); |
| 324 if (collector != null) { |
| 325 List<String> currentPaths = currentSubscriptions[service]; |
| 326 List<String> newPaths = newSubscriptions[service]; |
| 327 if (currentPaths == null) { |
| 328 if (newPaths == null) { |
| 329 // This should not happen. |
| 330 return; |
| 331 } |
| 332 // All of the [newPaths] need to be added. |
| 333 newPaths.forEach((String filePath) { |
| 334 collector.startCollectingFor(filePath); |
| 335 }); |
| 336 } else if (newPaths == null) { |
| 337 // All of the [currentPaths] need to be removed. |
| 338 currentPaths.forEach((String filePath) { |
| 339 collector.stopCollectingFor(filePath); |
| 340 }); |
| 341 } else { |
| 342 // Compute the difference of the two sets. |
| 343 newPaths.forEach((String filePath) { |
| 344 if (!currentPaths.contains(filePath)) { |
| 345 collector.startCollectingFor(filePath); |
| 346 } |
| 347 }); |
| 348 currentPaths.forEach((String filePath) { |
| 349 if (!newPaths.contains(filePath)) { |
| 350 collector.stopCollectingFor(filePath); |
| 351 } |
| 352 }); |
| 353 } |
| 354 } |
| 355 }); |
| 356 currentSubscriptions = newSubscriptions; |
| 357 } |
| 358 |
| 359 /** |
| 360 * Return `true` if errors should be collected for the file with the given |
| 361 * [path] (because it is being analyzed). |
| 362 */ |
| 363 bool _isIncluded(String path) { |
| 364 bool isIncluded() { |
| 365 for (String includedPath in includedPaths) { |
| 366 if (provider.pathContext.isWithin(includedPath, path) || |
| 367 provider.pathContext.equals(includedPath, path)) { |
| 368 return true; |
| 369 } |
| 370 } |
| 371 return false; |
| 372 } |
| 373 |
| 374 bool isExcluded() { |
| 375 for (String excludedPath in excludedPaths) { |
| 376 if (provider.pathContext.isWithin(excludedPath, path)) { |
| 377 return true; |
| 378 } |
| 379 } |
| 380 return false; |
| 381 } |
| 382 |
| 383 // TODO(brianwilkerson) Return false if error notifications are globally |
| 384 // disabled. |
| 385 return isIncluded() && !isExcluded(); |
| 386 } |
| 387 } |
| OLD | NEW |