Flutter数据分析插件candies_analyzer_plugin的使用
Flutter数据分析插件candies_analyzer_plugin的使用
描述
该插件用于帮助快速创建自定义的分析工具。
简单使用
在pubspec.yaml
中添加依赖
dev_dependencies:
# zmtzawqlp
candies_analyzer_plugin: any
在analysis_options.yaml
中启用插件
analyzer:
# zmtzawqlp
plugins:
candies_analyzer_plugin
默认的分析项包括:
- prefer_asset_const
- prefer_named_routes
- prefer_safe_setState
- must_call_super_dispose
- perfer_doc_comments
- prefer_singleton
- good_doc_comments
- prefer_trailing_comma
更多详情请参见默认分析项
自定义你的分析插件
创建模板
- 激活插件
运行以下命令来激活插件:
dart pub global activate candies_analyzer_plugin
- 进入项目目录
假设你的项目名为example
,你的分析插件名为custom_lint
。运行以下命令生成简单的分析插件:
candies_analyzer_plugin --example custom_lint
- 在项目的根目录
pubspec.yaml
中添加custom_lint
到dev_dependencies
dev_dependencies:
# zmtzawqlp
custom_lint:
path: custom_lint/
- 在项目的根目录
analysis_options.yaml
中添加custom_lint
到analyzer plugins
analyzer:
# zmtzawqlp
plugins:
custom_lint
完成分析后,你将在IDE中看到一些自定义的分析项。
添加你的分析项
找到基于以下项目结构的plugin.dart
文件:
├─ example
│ ├─ custom_lint
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ bin
│ │ │ └─ plugin.dart
plugin.dart
是插件的入口点。
启动插件
在该文件中启动插件:
CandiesAnalyzerPlugin get plugin => CustomLintPlugin();
// This file must be 'plugin.dart'
void main(List<String> args, SendPort sendPort) {
CandiesAnalyzerPluginStarter.start(
args,
sendPort,
plugin: plugin,
);
}
class CustomLintPlugin extends CandiesAnalyzerPlugin {
[@override](/user/override)
String get name => 'custom_lint';
[@override](/user/override)
List<String> get fileGlobsToAnalyze => const [
'**/*.dart',
'**/*.yaml',
'**/*.json',
];
[@override](/user/override)
List<DartLint> get dartLints => [
// 添加你的Dart分析项
PerferCandiesClassPrefix(),
...super.dartLints,
];
[@override](/user/override)
List<YamlLint> get yamlLints => [
RemoveDependency(package: 'path'),
];
[@override](/user/override)
List<GenericLint> get genericLints => [
RemoveDuplicateValue(),
];
}
创建一个分析项
你只需要创建一个继承自DartLint
、YamlLint
或GenericLint
的自定义分析项。
属性:
属性名 | 描述 | 默认值 |
---|---|---|
code | 错误代码的名称 | 必填 |
message | 显示的消息 | 必填 |
url | 错误相关的文档页面的URL | 可选 |
type | 错误类型 | 默认为LINT |
severity | 错误的严重性 | 默认为INFO |
correction | 修复消息 | 可选 |
contextMessages | 提供上下文信息的附加消息 | 可选 |
重要方法:
方法名 | 描述 | 是否覆盖 |
---|---|---|
matchLint | 返回是否匹配分析项 | 必须覆盖 |
getDartFixes/getYamlFixes/getGenericFixes | 返回修复项 | 不支持Yaml和Generic的修复项 |
Dart分析项
你可以通过覆盖ignoreLint
和ignoreFile
来忽略分析项或忽略文件。以下是一个Dart分析项的示例:
class PerferCandiesClassPrefix extends DartLint {
[@override](/user/override)
String get code => 'perfer_candies_class_prefix';
[@override](/user/override)
String? get url => 'https://github.com/fluttercandies/candies_analyzer_plugin';
[@override](/user/override)
SyntacticEntity? matchLint(AstNode node) {
if (node is ClassDeclaration) {
final String name = node.name2.toString();
final int startIndex = _getClassNameStartIndex(name);
if (!name.substring(startIndex).startsWith('Candies')) {
return node.name2;
}
}
return null;
}
[@override](/user/override)
String get message => '定义类名以Candies开头';
[@override](/user/override)
Future<List<SourceChange>> getDartFixes(
DartAnalysisError error,
CandiesAnalyzerPluginConfig config,
) async {
final ResolvedUnitResult resolvedUnitResult = error.result;
final AstNode astNode = error.astNode;
final Token nameNode = (astNode as ClassDeclaration).name2;
final String nameString = nameNode.toString();
return [
await getDartFix(
resolvedUnitResult: resolvedUnitResult,
message: '使用Candies作为类前缀。',
buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) {
final int startIndex = _getClassNameStartIndex(nameString);
final RegExp regExp = RegExp(nameString);
final String replace = '${nameString.substring(0, startIndex)}Candies${nameString.substring(startIndex)}';
for (final Match match in regExp.allMatches(resolvedUnitResult.content)) {
dartFileEditBuilder.addSimpleReplacement(
SourceRange(match.start, match.end - match.start),
replace,
);
}
dartFileEditBuilder.formatAll(resolvedUnitResult.unit);
},
)
];
}
int _getClassNameStartIndex(String nameString) {
int index = 0;
while (nameString[index] == '_') {
index++;
if (index == nameString.length - 1) {
break;
}
}
return index;
}
}
Yaml分析项
以下是一个Yaml分析项的示例:
class RemoveDependency extends YamlLint {
RemoveDependency({required this.package});
final String package;
[@override](/user/override)
String get code => 'remove_${package}_dependency';
[@override](/user/override)
String get message => '不要使用$package!';
[@override](/user/override)
String? get correction => '移除$package依赖';
[@override](/user/override)
AnalysisErrorSeverity get severity => AnalysisErrorSeverity.WARNING;
[@override](/user/override)
Iterable<SourceRange> matchLint(
YamlNode root,
String content,
LineInfo lineInfo,
) sync* {
if (root is YamlMap && root.containsKey(PubspecField.DEPENDENCIES_FIELD)) {
final YamlNode dependencies = root.nodes[PubspecField.DEPENDENCIES_FIELD]!;
if (dependencies is YamlMap && dependencies.containsKey(package)) {
final YamlNode get = dependencies.nodes[package]!;
int start = dependencies.span.start.offset;
final int end = get.span.start.offset;
final int index = content.substring(start, end).indexOf('$package: ');
start += index;
yield SourceRange(start, get.span.end.offset - start);
}
}
}
}
通用分析项
以下是一个通用分析项的示例:
class RemoveDuplicateValue extends GenericLint {
[@override](/user/override)
String get code => 'remove_duplicate_value';
[@override](/user/override)
Iterable<SourceRange> matchLint(
String content,
String file,
LineInfo lineInfo,
) sync* {
if (isFileType(file: file, type: '.json')) {
final Map<dynamic, dynamic> map = jsonDecode(content) as Map<dynamic, dynamic>;
final Map<dynamic, dynamic> duplicate = {};
final Map<dynamic, dynamic> checkDuplicate = {};
for (final dynamic key in map.keys) {
final dynamic value = map[key];
if (checkDuplicate.containsKey(value)) {
duplicate[key] = value;
duplicate[checkDuplicate[value]] = value;
}
checkDuplicate[value] = key;
}
if (duplicate.isNotEmpty) {
for (final dynamic key in duplicate.keys) {
final int start = content.indexOf('"$key"');
final dynamic value = duplicate[key];
final int end = content.indexOf(
'"$value"',
start,
) +
value.toString().length +
1;
final int lineNumber = lineInfo.getLocation(end).lineNumber;
bool hasComma = false;
int commaIndex = end;
int commaLineNumber = lineInfo.getLocation(commaIndex).lineNumber;
while (!hasComma && commaLineNumber == lineNumber) {
commaIndex++;
final String char = content[commaIndex];
hasComma = char == ',';
commaLineNumber = lineInfo.getLocation(commaIndex).lineNumber;
}
yield SourceRange(start, (hasComma ? commaIndex : end) + 1 - start);
}
}
}
}
[@override](/user/override)
String get message => '删除重复值';
}
调试
调试分析项
找到基于以下项目结构的debug.dart
文件:
├─ example
│ ├─ custom_lint
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ bin
│ │ │ └─ debug.dart
更改根目录为你想要调试的目录,默认是example
文件夹。
import 'dart:io';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart';
import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart';
import 'plugin.dart';
Future<void> main(List<String> args) async {
final String root = Directory.current.parent.parent.parent.path;
final AnalysisContextCollection collection = AnalysisContextCollection(includedPaths: [root]);
final CandiesAnalyzerPlugin myPlugin = plugin;
for (final AnalysisContext context in collection.contexts) {
for (final String file in context.contextRoot.analyzedFiles()) {
if (!myPlugin.shouldAnalyzeFile(file, context)) {
continue;
}
final bool isAnalyzed = context.contextRoot.isAnalyzed(file);
if (!isAnalyzed) {
continue;
}
final List<AnalysisError> errors = (await myPlugin.getAnalysisErrorsForDebug(
file,
context,
)).toList();
for (final AnalysisError error in errors) {
final List<AnalysisErrorFixes> fixes = await myPlugin
.getAnalysisErrorFixesForDebug(
EditGetFixesParams(file, error.location.offset), context)
.toList();
print(fixes.length);
}
print(errors.length);
}
}
}
更新代码
有两种方式更新新的代码到dartServer
:
- 删除
.plugin_manager
文件夹
注意,analyzer_plugin
文件夹会被复制到.plugin_manager
并根据加密插件路径创建一个文件夹。
macOS: /Users/user_name/.dartServer/.plugin_manager/
Windows: C:\Users\user_name\AppData\Local\.dartServer\.plugin_manager\
如果代码有变化,请删除plugin_manager
下的文件。
或者可以运行candies_analyzer_plugin --clear-cache
来删除plugin_manager
下的文件。
- 在
custom_lint
文件夹下写新的代码
你可以在custom_lint
下编写新的代码,例如在custom_lint.dart
中。
├─ example
│ ├─ custom_lint
│ │ ├─ lib
│ │ │ └─ custom_lint.dart
所以你应该在analyzer_plugin\pubspec.yaml
中添加custom_lint
依赖。
由于analyzer_plugin
文件夹会被复制到.plugin_manager
,你应该使用绝对路径。
如果你不打算发布custom_lint
作为新包,我不建议这样做。
├─ example
│ ├─ custom_lint
│ │ ├─ lib
│ │ │ └─ custom_lint.dart
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ analysis_options.yaml
dependencies:
custom_lint:
# 绝对路径
path: xxx/xxx/custom_lint
candies_analyzer_plugin: any
path: any
analyzer: any
analyzer_plugin: any
重启服务器
更新代码后,你需要通过以下步骤重启分析服务器:
-
找到
Command Palette
在View
-
输入
Restart Analysis Server
现在你可以看到新的改动了。
日志
-
性能方面,默认为false,如果你想检查日志,设置为true。你可以在以下位置打开日志:
CandiesAnalyzerPluginLogger().shouldLog = true;
在项目中会生成
custom_lint.log
文件。 -
你可以自定义日志名称
CandiesAnalyzerPluginLogger().logFileName = 'your name';
-
记录信息
CandiesAnalyzerPluginLogger().log( 'info', // 日志文件生成的位置 root: result.root, );
-
记录错误
CandiesAnalyzerPluginLogger().logError( 'analyze file failed:', root: analysisContext.root, error: e, stackTrace: stackTrace, );
配置
禁用一个分析项
默认情况下,所有自定义分析项都是启用的。你也可以在analysis_options.yaml
中编写配置来禁用它们。
-
为分析项添加忽略
analyzer: errors: perfer_candies_class_prefix: ignore
-
排除文件
analyzer: exclude: - lib/exclude/*.dart
-
禁用一个分析项
linter: rules: # 禁用一个分析项 perfer_candies_class_prefix: false
包含
我们可以在custom_lint
(即你的插件名)下定义include
标签。这意味着我们只分析包含的文件。
# 你的插件名
custom_lint:
# 如果我们定义了这个,我们就只分析包含的文件
include:
- lib/include/*.dart
自定义分析项的严重性
你可以通过以下设置改变分析项的严重性。
将perfer_candies_class_prefix
的严重性从info
改为warning
。
支持warning
、info
、error
。
analyzer:
errors:
# 覆盖错误严重性
perfer_candies_class_prefix: warning
默认分析项
PerferClassPrefix
定义一个类名以前缀开头
class PerferClassPrefix extends DartLint {
PerferClassPrefix(this.prefix);
final String prefix;
[@override](/user/override)
String get code => 'perfer_${prefix}_class_prefix';
}
PreferAssetConst
偏好使用资产常量而不是字符串。
class PreferAssetConst extends DartLint {
[@override](/user/override)
String get code => 'prefer_asset_const';
[@override](/user/override)
String? get url => 'https://pub.dev/packages/assets_generator';
}
PreferNamedRoutes
偏好使用命名路由。
class PreferNamedRoutes extends DartLint {
[@override](/user/override)
String get code => 'prefer_named_routes';
[@override](/user/override)
String? get url => 'https://pub.dev/packages/ff_annotation_route';
}
PerferSafeSetState
偏好在调用setState
之前检查mounted
状态。
class PerferSafeSetState extends DartLint {
[@override](/user/override)
String get code => 'prefer_safe_setState';
}
MustCallSuperDispose
实现此方法的实现应以调用继承的方法结束,如super.dispose()
。
class MustCallSuperDispose extends DartLint with CallSuperDisposeMixin {
[@override](/user/override)
String get code => 'must_call_super_dispose';
}
EndCallSuperDispose
应该在该方法末尾调用super.dispose()
。
class EndCallSuperDispose extends DartLint with CallSuperDisposeMixin {
[@override](/user/override)
String get code => 'end_call_super_dispose';
}
PerferDocComments
偏好使用文档注释。
class PerferDocComments extends DartLint {
[@override](/user/override)
String get code => 'perfer_doc_comments';
}
PreferSingleton
这不是单例,每次都会创建新对象。
class PreferSingleton extends DartLint {
[@override](/user/override)
String get code => 'prefer_singleton';
}
GoodDocComments
错误的注释格式。(/// xxx)用于公共API,(// xxx)用于其他情况。
class GoodDocComments extends DartLint {
[@override](/user/override)
String get code => 'good_doc_comments';
}
PreferTrailingComma
偏好使用尾随逗号以获得更好的代码风格。
class PreferTrailingComma extends DartLint {
[@override](/user/override)
String get code => 'prefer_trailing_comma';
}
完成
创建自定义完成项
你可以在completionContributors
中定义自己的CompletionContributor
,默认的是ExtensionMemberContributor
。
/// The completionContributors to finish CompletionRequest
List<CompletionContributor> get completionContributors => [
ExtensionMemberContributor(),
];
扩展成员的建议
尽管Dart团队已经关闭了问题[Auto import (or quickfix?) for Extensions · Issue #38894 · dart-lang/sdk (github.com)],但在不同IDE开发时仍然存在许多问题。
ExtensionMemberContributor
有助于轻松处理扩展成员。
预提交
pre_commit.dart
找到基于以下项目结构的pre_commit.dart
文件,这是一个在提交代码前检查错误的示例。
├─ example
│ ├─ custom_lint
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ bin
│ │ │ └─ pre_commit.dart
预提交脚本
运行candies_analyzer_plugin --pre-commit
,预提交脚本将在.git/hooks
下生成。
你可以在项目中添加一个pre-commit
文件来自定义这个脚本模板。
├─ example
│ ├─ pre-commit
{0}
和{1}
是占位符,不要修改它们。
#!/bin/sh
# 项目路径
base_dir="{0}"
dart format "$base_dir"
# pre_commit.dart路径
pre_commit="{1}"
echo "正在检查代码提交前的状态..."
echo "正在分析$base_dir..."
info=$(dart "$pre_commit" "$base_dir")
echo "$info"
if [[ -n $info && $info != *"No issues found"* ]];then
exit 1
fi
当你运行git commit
命令提交代码时,它将首先在.git/hooks/pre-commit
脚本中运行。
然后该脚本将调用example/custom_lint/tools/analyzer_plugin/bin/pre_commit.dart
,如果有错误,你应该退出1。
你可以修改example/pre-commit
和example/custom_lint/tools/analyzer_plugin/bin/pre_commit.dart
来自定义你的规则。
缓存错误到文件
设置CandiesAnalyzerPlugin.cacheErrorsIntoFile
为true,以减少在提交代码前检查错误所花费的时间。
注意事项
打印延迟
在插件分析过程中不要写print
语句,这会导致分析延迟。
pubspec.yaml和analysis_options.yaml
你必须做以下事情来支持你的项目进行分析:
-
在
pubspec.yaml
的dev_dependencies
中添加custom_lint
,参见pubspec.yaml -
在
analysis_options.yaml
的analyzer plugins
中添加custom_lint
,参见analysis_options.yaml
仅在vscode中支持Dart文件的快速修复(Android Studio支持任何类型的文件)
在vscode中自动导入不工作
示例代码
// ignore_for_file: unused_local_variable
import 'package:example/singleton.dart';
import 'package:flutter/material.dart';
import 'package:flutter_package/flutter_package.dart' as flutter_package;
import 'test.dart' as test;
void main() {
int i = 1;
i.f(1);
flutter_package.TestApi.common.aaa();
Singleton().printInfo();
var num = Singleton().num;
const Singleton1().printInfo();
var num1 = const Singleton1().num;
test.Test test1 = test.Test();
List<int> list = [];
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void test() {
setState(() {
_counter++;
});
}
[@override](/user/override)
void dispose() {
super.dispose();
int i = 1;
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
Image.asset('name'),
GestureDetector(
onTap: () => setState(() {}),
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (b) => Container()),
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
更多关于Flutter数据分析插件candies_analyzer_plugin的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter数据分析插件candies_analyzer_plugin的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用candies_analyzer_plugin
插件进行数据分析的示例代码案例。请注意,这个示例假设你已经有一个Flutter项目,并且已经在pubspec.yaml
文件中添加了candies_analyzer_plugin
依赖。
首先,确保你的pubspec.yaml
文件中包含以下依赖项:
dependencies:
flutter:
sdk: flutter
candies_analyzer_plugin: ^最新版本号 # 请替换为实际的最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Flutter项目中,你可以按照以下步骤使用candies_analyzer_plugin
进行数据分析。
1. 导入插件
在你的Dart文件中导入插件:
import 'package:candies_analyzer_plugin/candies_analyzer_plugin.dart';
2. 初始化插件
通常,你会在应用的入口文件(如main.dart
)中初始化插件:
void main() {
WidgetsFlutterBinding.ensureInitialized();
// 初始化插件
CandiesAnalyzerPlugin.instance.initialize();
runApp(MyApp());
}
3. 使用插件进行数据分析
假设你想要分析一些用户行为数据,你可以创建一个方法来收集和处理这些数据。以下是一个简单的示例,展示如何使用插件提供的功能:
class DataAnalyzer {
CandiesAnalyzerPlugin _analyzerPlugin = CandiesAnalyzerPlugin.instance;
Future<void> analyzeUserBehavior(Map<String, dynamic> userData) async {
try {
// 假设插件提供了一个分析用户行为的方法
await _analyzerPlugin.analyze(userData);
print("User behavior data analyzed successfully.");
} catch (e) {
print("Error analyzing user behavior data: $e");
}
}
}
4. 在UI中触发数据分析
在你的UI组件中,你可以触发数据分析。例如,在一个按钮点击事件中:
class MyApp extends StatelessWidget {
final DataAnalyzer _dataAnalyzer = DataAnalyzer();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Candies Analyzer Plugin Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
// 创建一些示例用户数据
Map<String, dynamic> userData = {
'userId': '12345',
'action': 'page_view',
'timestamp': DateTime.now().toIso8601String(),
'page': '/home',
};
// 分析用户行为数据
await _dataAnalyzer.analyzeUserBehavior(userData);
},
child: Text('Analyze User Behavior'),
),
),
),
);
}
}
注意事项
- 上述代码中的
CandiesAnalyzerPlugin.instance.analyze(userData);
是一个假设的方法调用。实际使用时,你需要参考candies_analyzer_plugin
的文档来确定正确的方法调用和参数。 - 插件的具体功能和使用方式可能会有所不同,因此务必查阅最新的官方文档或插件的README文件。
- 确保你已经正确配置了任何必要的权限(如网络访问权限),这取决于插件的具体需求。
这个示例提供了一个基本的框架,展示了如何在Flutter项目中集成和使用candies_analyzer_plugin
进行数据分析。根据插件的实际功能和需求,你可能需要调整代码以适应你的具体用例。