| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| import 'dart:async'; |
| |
| import 'package:analyzer/context/context_root.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/src/context/builder.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/dart/analysis/performance_logger.dart'; |
| import 'package:analyzer_plugin/plugin/plugin.dart'; |
| import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin; |
| import 'package:analyzer_plugin/protocol/protocol_constants.dart' as plugin; |
| import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin; |
| import 'package:analyzer_plugin/plugin/completion_mixin.dart'; |
| import 'package:analyzer_plugin/plugin/navigation_mixin.dart'; |
| import 'package:analyzer_plugin/utilities/analyzer_converter.dart'; |
| import 'package:analyzer_plugin/utilities/completion/completion_core.dart'; |
| import 'package:analyzer_plugin/utilities/navigation/navigation.dart'; |
| import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart'; |
| import 'package:analyzer_plugin/src/utilities/completion/completion_core.dart'; |
| import 'package:angular_analyzer_plugin/src/notification_manager.dart'; |
| import 'package:angular_analyzer_plugin/src/completion_request.dart'; |
| import 'package:angular_analyzer_plugin/src/navigation_request.dart'; |
| import 'package:angular_analyzer_plugin/src/angular_driver.dart'; |
| import 'package:angular_analyzer_plugin/src/completion.dart'; |
| import 'package:angular_analyzer_plugin/src/navigation.dart'; |
| import 'package:angular_analyzer_plugin/src/options.dart'; |
| import 'package:analyzer_plugin/protocol/protocol.dart' as plugin; |
| import 'package:meta/meta.dart'; |
| |
| class AngularAnalyzerPlugin extends ServerPlugin |
| with CompletionMixin, NavigationMixin { |
| AngularAnalyzerPlugin(ResourceProvider provider) : super(provider); |
| |
| @override |
| List<String> get fileGlobsToAnalyze => <String>['*.dart', '*.html']; |
| |
| @override |
| String get name => 'Angular Analysis Plugin'; |
| |
| @override |
| String get version => '1.0.0-alpha.0'; |
| |
| @override |
| String get contactInfo => |
| 'Please file issues at https://github.com/dart-lang/angular_analyzer_plugin'; |
| |
| AngularOptions getOptions(String optionsFilePath) { |
| if (optionsFilePath != null && optionsFilePath.isNotEmpty) { |
| final file = resourceProvider.getFile(optionsFilePath); |
| if (file.exists) { |
| return new AngularOptions.from(file.createSource()); |
| } |
| } |
| return new AngularOptions.defaults(); |
| } |
| |
| @override |
| AnalysisDriverGeneric createAnalysisDriver(plugin.ContextRoot contextRoot) { |
| final root = new ContextRoot(contextRoot.root, contextRoot.exclude) |
| ..optionsFilePath = contextRoot.optionsFile; |
| final options = getOptions(root.optionsFilePath); |
| |
| final logger = new PerformanceLog(new StringBuffer()); |
| final builder = new ContextBuilder(resourceProvider, sdkManager, null) |
| ..analysisDriverScheduler = (new AnalysisDriverScheduler(logger)..start()) |
| ..byteStore = byteStore |
| ..performanceLog = logger |
| ..fileContentOverlay = fileContentOverlay; |
| |
| final dartDriver = builder.buildDriver(root) |
| ..results.listen((_) {}) // Consume the stream, otherwise we leak. |
| ..exceptions.listen((_) {}); // Consume the stream, otherwise we leak. |
| |
| final sourceFactory = dartDriver.sourceFactory; |
| |
| final driver = new AngularDriver( |
| resourceProvider, |
| // TODO(mfairhurst) remove NotificationManager & old plugin loader. |
| new NoopNotificationManager(), |
| dartDriver, |
| analysisDriverScheduler, |
| byteStore, |
| sourceFactory, |
| fileContentOverlay, |
| options); |
| |
| driver.dartResultsStream |
| .listen((result) => onResult(result, driver, isHtml: false)); |
| driver.htmlResultsStream |
| .listen((result) => onResult(result, driver, isHtml: true)); |
| return driver; |
| } |
| |
| void onResult(DirectivesResult result, AngularDriver driver, |
| {@required bool isHtml}) { |
| _handleResultErrors(result, driver); |
| _handleResultNavigation(result, driver, isHtml: isHtml); |
| } |
| |
| /// Send notifications for errors for this result |
| void _handleResultErrors(DirectivesResult result, AngularDriver driver) { |
| final converter = new AnalyzerConverter(); |
| final lineInfo = |
| new LineInfo.fromContent(driver.getFileContent(result.filename)); |
| // TODO(mfairhurst) Get the right analysis options. |
| final errors = converter.convertAnalysisErrors( |
| result.errors, |
| lineInfo: lineInfo, |
| ); |
| channel.sendNotification( |
| new plugin.AnalysisErrorsParams(result.filename, errors) |
| .toNotification()); |
| } |
| |
| /// Send notifications for navigation for this result |
| void _handleResultNavigation(DirectivesResult result, AngularDriver driver, |
| {@required bool isHtml}) { |
| final collector = new NavigationCollectorImpl(); |
| final filename = result.filename; |
| if (filename == null || |
| !subscriptionManager.hasSubscriptionForFile( |
| filename, plugin.AnalysisService.NAVIGATION)) { |
| return; |
| } |
| |
| if (result.cacheResult) { |
| // get a non-cached result, so we have an AST. |
| // TODO(mfairhurst) make this assurance in a less hacky way |
| isHtml |
| ? driver.requestHtmlResult(filename) |
| : driver.requestDartResult(filename); |
| return; |
| } |
| |
| new AngularNavigation(driver.contentOverlay) |
| ..computeNavigation( |
| new AngularNavigationRequest(filename, null, null, result), |
| collector); |
| collector.createRegions(); |
| channel.sendNotification(new plugin.AnalysisNavigationParams( |
| filename, collector.regions, collector.targets, collector.files) |
| .toNotification()); |
| } |
| |
| /// Return the navigation request that should be passed to the contributors |
| /// returned from [getNavigationContributors]. |
| @override |
| Future<NavigationRequest> getNavigationRequest( |
| plugin.AnalysisGetNavigationParams parameters) async { |
| final AngularDriver driver = driverForPath(parameters.file); |
| final isHtml = parameters.file.endsWith('.html'); |
| final result = isHtml |
| ? await driver.requestHtmlResult(parameters.file) |
| : await driver.requestDartResult(parameters.file); |
| return new AngularNavigationRequest( |
| parameters.file, parameters.offset, parameters.length, result); |
| } |
| |
| /// Return a list containing the navigation contributors that should be used to |
| /// create navigation information when used in the context of the given |
| /// analysis [driver]. |
| @override |
| List<NavigationContributor> getNavigationContributors(String path) => |
| [new AngularNavigation(angularDriverForPath(path).contentOverlay)]; |
| |
| void sendNotificationForSubscription( |
| String fileName, plugin.AnalysisService service, AnalysisResult result) { |
| switch (service) { |
| case plugin.AnalysisService.FOLDING: |
| // TODO(brianwilkerson) Implement this. |
| break; |
| case plugin.AnalysisService.HIGHLIGHTS: |
| // TODO(brianwilkerson) Implement this. |
| break; |
| case plugin.AnalysisService.NAVIGATION: |
| // TODO(brianwilkerson) Implement this. |
| break; |
| case plugin.AnalysisService.OCCURRENCES: |
| // TODO(brianwilkerson) Implement this. |
| break; |
| case plugin.AnalysisService.OUTLINE: |
| // TODO(brianwilkerson) Implement this. |
| break; |
| default: |
| // Ignore unhandled service types. |
| break; |
| } |
| } |
| |
| AngularDriver angularDriverForPath(String path) { |
| final driver = super.driverForPath(path); |
| if (driver is AngularDriver) { |
| return driver; |
| } |
| return null; |
| } |
| |
| @override |
| void contentChanged(String path) { |
| final driver = angularDriverForPath(path); |
| if (driver == null) { |
| return; |
| } |
| |
| driver |
| ..addFile(path) // TODO new API to only do this on file add |
| ..fileChanged(path); |
| |
| driver.dartDriver |
| ..addFile(path) // TODO new API to only do this on file add |
| ..changeFile(path); |
| } |
| |
| /// The method that is called when an error has occurred in the analysis |
| /// server. This method will not be invoked under normal conditions. |
| @override |
| void onError(Object exception, StackTrace stackTrace) { |
| print('Communication Exception: $exception\n$stackTrace'); |
| // ignore: only_throw_errors |
| throw exception; |
| } |
| |
| @override |
| void sendNotificationsForSubscriptions( |
| Map<String, List<plugin.AnalysisService>> subscriptions) { |
| subscriptions.forEach((filePath, services) { |
| // TODO(brianwilkerson) Get the results for this file. |
| AnalysisResult result; |
| for (final service in services) { |
| sendNotificationForSubscription(filePath, service, result); |
| } |
| }); |
| } |
| |
| @override |
| Future<CompletionRequest> getCompletionRequest( |
| plugin.CompletionGetSuggestionsParams parameters) async { |
| final path = parameters.file; |
| final driver = angularDriverForPath(path); |
| final offset = parameters.offset; |
| |
| if (driver == null) { |
| return new DartCompletionRequestImpl(resourceProvider, offset, null); |
| } |
| |
| final templates = await driver.getTemplatesForFile(path); |
| final standardHtml = await driver.getStandardHtml(); |
| assert(standardHtml != null); |
| return new AngularCompletionRequest( |
| offset, path, resourceProvider, templates, standardHtml); |
| } |
| |
| @override |
| List<CompletionContributor> getCompletionContributors(String path) { |
| if (angularDriverForPath(path) == null) { |
| return []; |
| } |
| |
| return <CompletionContributor>[ |
| new AngularCompletionContributor(), |
| new NgInheritedReferenceContributor(), |
| new NgTypeMemberContributor(), |
| new NgOffsetLengthContributor(), |
| ]; |
| } |
| } |