完善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,缓存目录位于:
直接整个目录删除即可。
然后重新引入plugin,重启Analyzer Server。随意编辑一些dart文件,IDE会自动重新执行analyze分析,然后过一会就能在日志目录下,看到日志文件了,内容如下: