Dart-自定义Lint之路(二)-运行和调试Analyzer Plugin

735 阅读3分钟
  1. # Dart-自定义Lint之路(一)-创建Analyzer Plugin

完善MyPlugin

之前已经创建了一个简单的MyPlugin插件:

import 'package:analyzer/file_system/file_system.dart'; 
import 'package:analyzer/src/dart/analysis/driver.dart'; 
import 'package:analyzer_plugin/plugin/plugin.dart'; 
import 'package:analyzer_plugin/protocol/protocol_generated.dart';   
class MyPlugin extends ServerPlugin {   
    MyPlugin(ResourceProvider provider) : super(provider);     
    
    @override   
    List<String> get fileGlobsToAnalyze => <String>['**/*.dart']; 
    
    @override   
    String get name => 'My analyzer plugin';     
    
    @override   
    String get version => '1.0.0';     
    
    @override   
    void contentChanged(String path) { 	
        // TODO: implement contentChanged     
        super.contentChanged(path);   
    }    
    
    @override   
    AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot) {     
    // TODO: implement createAnalysisDriver     
    return null;   
    } 
}

这次就完善MyPlugin,让它真正跑起来。

Plugin框架说明

首先说明ServerPlugin中的那些需要覆写的方法都是什么含义:

  • fileGlobsToAnalyze: 用于设置该plugin需要接收哪些文件进行分析。

  • name: Plugin插件名

  • version: Plugin插件版本

  • contentChanged: 每当有文件内容变化或添加或删除,就会触发该方法

  • createAnalysisDriver: 创建一个分析器

name和version不做说明。fileGlobsToAnalyze没什么意外的话,就是分析dart文件,可能会排除一些特定目录下的dart文件。

contentChanged一般的实现如下:

  @override   
  void contentChanged(String path) {     
      //有文件改动,通知给driver     
      super.driverForPath(path)?.addFile(path);   
  }

意思就是,找到可以分析该path的分析器,然后把这个path交给这个分析器去分析。

createAnalysisDriver用于创建分析器,代码如下:

  @override   
  AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot) {
      final rootPath = contextRoot.root;     
      final locator = ContextLocator(resourceProvider: resourceProvider,).locateRoots(includedPaths: [rootPath],excludedPaths: contextRoot.exclude, optionsFile: contextRoot.optionsFile,);
      final builder = ContextBuilder(resourceProvider: resourceProvider);
      final context = builder.createContext(contextRoot: locator.first)  as DriverBasedAnalysisContext;     
      final dartDriver = context.driver;     
      //启动沙盒去监听处理文件改动     
      runZonedGuarded(() {       
          dartDriver.results.listen((analysisResult) {
              //todo 每次有文件改动或新建文件,就会触发这里
              });
          }, (error, trace) {
              //如果插件运行出错了       
              channel.sendNotification(PluginErrorParams(true, error.toString(), trace.toString()).toNotification());     
              }
       );     
       return dartDriver;   
}

至此,MyPlugin的框架代码基本实现完成。那么接下来就是运行和调试这个MyPlugin。

调试Plugin

完善好Plugin之后,这个Plugin就能够真正跑起来了, 但对于Plugin功能的开发还未实现,对于网上找不到完善资料的情况下,那么就少不了调试法的方法,来逐步开发功能。

但是非常可惜的是,目前Analyzer Plugin的调试功能非常弱,官方Debug文档也有说明,不支持断点调试,也不支持print日志输出。但可以通过写文件的方式,进行日志记录。这里就推荐一个三方库:

dependencies: 	quick_log: any

然后进行功能封装:

import 'dart:convert';
import 'dart:io';
import "package:path/path.dart" as p;
import 'package:quick_log/quick_log.dart';

class QLogger extends Logger {
  final String filePath;
  const QLogger(String name, this.filePath) : super(name, 'MirrorLogger');

  factory QLogger.createOutputToFile(String name, {required String filePath}) {
    var logger = QLogger(name ?? 'Log', filePath);
    var fOutput = FileOutput(file: File(logger.filePath));
    fOutput.init();
    var w = LogStreamWriter();
    Logger.writer = w;
    w.messages.listen((LogMessage message) {
      fOutput.output('${message.timestamp} | ${message.message}');
    });
    return logger;
  }
}

class FileOutput {
  final File file;
  final bool overrideExisting;
  final Encoding encoding;
  late IOSink _sink;

  FileOutput({
    required this.file,
    this.overrideExisting = false,
    this.encoding = utf8,
  });

  void init() {
    _sink = file.openWrite(
      mode: overrideExisting ? FileMode.writeOnly : FileMode.writeOnlyAppend,
      encoding: encoding,
    );
  }

  void output(msg) {
    if (msg is Iterable) {
      _sink.writeAll(msg, '\n');
    } else if (msg is String) {
      _sink.writeln(msg);
    }
  }

  void destroy() async {
    await _sink.flush();
    await _sink.close();
  }
}

Future<void> writeAFile(String path) async {
  var myFile = File(path);
  myFile.createSync(recursive: true);
  var sink = myFile.openWrite();
  sink.write('hello plugin!');
  await sink.flush();
  await sink.close();
}

String getLogPath() {
  //TODO 这里填写日志保存地址
  String logFileDir = '';
  var storeFile = new File(p.absolute('$logFileDir/output.log'));
  if (!storeFile.existsSync()) {
    storeFile.createSync();
  }
  return p.absolute('$logFileDir/output.log');
}

class EmtpyLogger {
  void info(String message) {
    // do nothing
  }
}

var pluginLogger =
    QLogger.createOutputToFile('plugin', filePath: getLogPath()); // 开始log
// var pluginLogger = EmtpyLogger(); // 关闭log

这样,就有了一个pluginLogger来进行日志输出(保存到文件)。

只需要使用pluginLogger.info('xxxxxxxxxxx')就能将日志输出到文件,还是非常方便的。

在之前的runZonedGuarded中使用这个pluginLogger进行日志输出:

      runZonedGuarded(() {       
          dartDriver.results.listen((analysisResult) {
              pluginLogger.info('${analysisResult.runtimeType}');
          });
          }, (error, trace) {
              //如果插件运行出错了       
              channel.sendNotification(PluginErrorParams(true, error.toString(), trace.toString()).toNotification());     
              }
       );

然后如果使用之前的用于测试Plugin的项目,因为是使用本地依赖的方式导入的MyPlugin,直接修改更新MyPlugin的代码,并不会生效,原因虽然使用的本地依赖,但dart还是会拷贝一份plugin工程到缓存目录下去,然后再依赖这份缓存,所以在测试修改后的plugin前, 还需要删除缓存目录下的MyPlugin,缓存目录位于: image.png 直接整个目录删除即可。

然后重新引入plugin,重启Analyzer Server。随意编辑一些dart文件,IDE会自动重新执行analyze分析,然后过一会就能在日志目录下,看到日志文件了,内容如下:

image.png