0 purchases
candies analyzer plugin
candies_analyzer_plugin #
Languages: English | 中文简体
Description #
The plugin to help create custom lint quickly.
candies_analyzer_plugin
Description
simple use
add into pubspec.yaml
add into analysis_options.yaml
Custom your analyzer plugin
Create Template
Add your lint
start plugin
create a lint
dart lint
yaml lint
generic lint
Debug
debug lint
update code
restart server
Log
Config
disable a lint
include
custom lint severity
Default lints
PerferClassPrefix
PreferAssetConst
PreferNamedRoutes
PerferSafeSetState
MustCallSuperDispose
EndCallSuperDispose
PerferDocComments
PreferSingleton
GoodDocComments
PreferTrailingComma
Completion
Make a custom completion
Suggestions of Extension Member
Pre-Commit
pre_commit.dart
pre-commit script
cacheErrorsIntoFile
Note
print lag
pubspec.yaml and analysis_options.yaml
quick fixes are only supported for dart files in vscode.(android studio support any type of file)
completion auto import is not working in vscode
example
analyzer_plugin doc
simple use #
add into pubspec.yaml #
dev_dependencies:
# zmtzawqlp
candies_analyzer_plugin: any
copied to clipboard
add into analysis_options.yaml #
analyzer:
# zmtzawqlp
plugins:
candies_analyzer_plugin
copied to clipboard
default lints are following:
prefer_asset_const
prefer_named_routes
prefer_safe_setState
must_call_super_dispose
must_call_super_dispose
perfer_doc_comments
prefer_singleton
good_doc_comments
prefer_trailing_comma
more info please check [Default lints](#Default lints)
Custom your analyzer plugin #
Create Template #
activate plugin
run dart pub global activate candies_analyzer_plugin
cd to your project
Let us suppose:
your project is example
your lint plugin is custom_lint
run candies_analyzer_plugin --exmaple custom_lint, a simple lint plugin is generated.
add custom_lint into dev_dependencies of the root pubspec.yaml
dev_dependencies:
# zmtzawqlp
custom_lint:
path: custom_lint/
copied to clipboard
add custom_lint into analyzer plugins of the root analysis_options.yaml
analyzer:
# zmtzawqlp
plugins:
custom_lint
copied to clipboard
after analysis are finished, you will see some custom lint in your ide.
Add your lint #
find plugin.dart base on following project tree
├─ example
│ ├─ custom_lint
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ bin
│ │ │ └─ plugin.dart
copied to clipboard
plugin.dart is the entrance of plugin.
start plugin #
we start plugin in this file.
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
String get name => 'custom_lint';
@override
List<String> get fileGlobsToAnalyze => const <String>[
'**/*.dart',
'**/*.yaml',
'**/*.json',
];
@override
List<DartLint> get dartLints => <DartLint>[
// add your dart lint here
PerferCandiesClassPrefix(),
...super.dartLints,
];
@override
List<YamlLint> get yamlLints => <YamlLint>[RemoveDependency(package: 'path')];
@override
List<GenericLint> get genericLints => <GenericLint>[RemoveDuplicateValue()];
}
copied to clipboard
create a lint #
you just need to make a custom lint which extends from DartLint ,YamlLint, GenericLint.
Properties:
Property
Description
Default
code
The name, as a string, of the error code associated with this error.
required
message
The message to be displayed for this error. The message should indicate what is wrong with the code and why it is wrong.
required
url
The URL of a page containing documentation associated with this error.
type
The type of the error. CHECKED_MODE_COMPILE_TIME_ERRORCOMPILE_TIME_ERRORHINTLINTSTATIC_TYPE_WARNINGSTATIC_WARNINGSYNTACTIC_ERRORTODO
The default is LINT.
severity
The severity of the error.INFOWARNINGERROR
The default is INFO.
correction
The correction message to be displayed for this error. The correction message should indicate how the user can fix the error. The field is omitted if there is no correction message associated with the error code.
contextMessages
Additional messages associated with this diagnostic that provide context to help the user understand the diagnostic.
Important methodes:
Method
Description
Override
matchLint
return whether is match lint.
must
getDartFixes/getYamlFixes/getGenericFixes
return fixes if has.
getYamlFixes/getGenericFixes doesn't work for now, leave it in case dart team maybe support it someday in the future, see issue
dart lint
you can ignore lint or ignore file by override [ignoreLint] and [ignoreFile].
Here is a demo for a dart lint:
class PerferCandiesClassPrefix extends DartLint {
@override
String get code => 'perfer_candies_class_prefix';
@override
String? get url => 'https://github.com/fluttercandies/candies_analyzer_plugin';
@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
String get message => 'Define a class name start with Candies';
@override
Future<List<SourceChange>> getDartFixes(
DartAnalysisError error,
CandiesAnalyzerPluginConfig config,
) async {
final ResolvedUnitResult resolvedUnitResult = error.result;
final AstNode astNode = error.astNode;
// get name node
final Token nameNode = (astNode as ClassDeclaration).name2;
final String nameString = nameNode.toString();
return <SourceChange>[
await getDartFix(
resolvedUnitResult: resolvedUnitResult,
message: 'Use Candies as a class prefix.',
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;
}
}
copied to clipboard
yaml lint
Here is a demo for a yaml lint:
class RemoveDependency extends YamlLint {
RemoveDependency({required this.package});
final String package;
@override
String get code => 'remove_${package}_dependency';
@override
String get message => 'don\'t use $package!';
@override
String? get correction => 'Remove $package dependency';
@override
AnalysisErrorSeverity get severity => AnalysisErrorSeverity.WARNING;
@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);
}
}
}
}
copied to clipboard
generic lint
Here is a demo for a generic lint:
class RemoveDuplicateValue extends GenericLint {
@override
String get code => 'remove_duplicate_value';
@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 = <dynamic, dynamic>{};
final Map<dynamic, dynamic> checkDuplicate = <dynamic, dynamic>{};
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
String get message => 'remove duplicate value';
}
copied to clipboard
Debug #
debug lint #
find debug.dart base on following project tree
├─ example
│ ├─ custom_lint
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ bin
│ │ │ └─ debug.dart
copied to clipboard
change root to which you want to debug, default is example folder.
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: <String>[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);
}
}
}
copied to clipboard
update code #
├─ example
│ ├─ custom_lint
│ │ └─ tools
│ │ └─ analyzer_plugin
copied to clipboard
you have two options to update new code into dartServer.
delete .plugin_manager folder
Note, analyzer_plugin folder will be copyed into .plugin_manager and create a folder base on encrypt plugin path.
macos: /Users/user_name/.dartServer/.plugin_manager/
windows: C:\Users\user_name\AppData\Local\.dartServer\.plugin_manager\
if your code is changed, please remove the files under .plugin_manager.
or you can run candies_analyzer_plugin --clear-cache to remove the files under .plugin_manager.
write new code under custom_lint folder
you can write new code under custom_lint, for exmaple, in custom_lint.dart.
├─ example
│ ├─ custom_lint
│ │ ├─ lib
│ │ │ └─ custom_lint.dart
copied to clipboard
so you should add custom_lint dependencies into analyzer_plugin\pubspec.yaml
you should use absolute path due to analyzer_plugin folder will copy to .plugin_manager.
if your don't publish custom_lint as new package, i don't suggest do as this.
├─ example
│ ├─ custom_lint
│ │ ├─ lib
│ │ │ └─ custom_lint.dart
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ analysis_options.yaml
copied to clipboard
dependencies:
custom_lint:
# absolute path
path: xxx/xxx/custom_lint
candies_analyzer_plugin: any
path: any
analyzer: any
analyzer_plugin: any
copied to clipboard
restart server #
after update code, you should restart analysis server by following steps in vscode.
find Command Palette in View
enter Restart Analysis Server
now, you can see the new change.
Log #
for performance, default is false, if you want to check log, set it to true. you can open Log.
CandiesAnalyzerPluginLogger().shouldLog = true;
Under the project custom_lint.log will be generated.
you can custom log name
CandiesAnalyzerPluginLogger().logFileName = 'your name';
log info
CandiesAnalyzerPluginLogger().log(
'info',
// which location custom_lint.log will be generated
root: result.root,
);
copied to clipboard
log error
CandiesAnalyzerPluginLogger().logError(
'analyze file failed:',
root: analysisContext.root,
error: e,
stackTrace: stackTrace,
);
copied to clipboard
Config #
disable a lint #
As default, all of the custom lints are enable. And you can also write a config in analysis_options.yaml to disable they.
add ignore for a lint.
analyzer:
errors:
perfer_candies_class_prefix: ignore
copied to clipboard
exclude files
analyzer:
exclude:
- lib/exclude/*.dart
copied to clipboard
disable a lint
linter:
rules:
# disable a lint
perfer_candies_class_prefix: false
copied to clipboard
include #
we can define include tag under custom_lint (it's your plugin name).
it means that we only analyze the include files.
# your plugin name
custom_lint:
# if we define this, we only analyze include files
include:
- lib/include/*.dart
copied to clipboard
custom lint severity #
you can change lint severity by following setting.
change the severity of perfer_candies_class_prefix from info to warning.
support warning , info , error.
analyzer:
errors:
# override error severity
perfer_candies_class_prefix: warning
copied to clipboard
Default lints #
PerferClassPrefix #
Define a class name start with prefix
class PerferClassPrefix extends DartLint {
PerferClassPrefix(this.prefix);
final String prefix;
@override
String get code => 'perfer_${prefix}_class_prefix';
}
copied to clipboard
PreferAssetConst #
Prefer to use asset const instead of a string.
class PreferAssetConst extends DartLint {
@override
String get code => 'prefer_asset_const';
@override
String? get url => 'https://pub.dev/packages/assets_generator';
}
copied to clipboard
PreferNamedRoutes #
Prefer to use named routes.
class PreferNamedRoutes extends DartLint {
@override
String get code => 'prefer_named_routes';
@override
String? get url => 'https://pub.dev/packages/ff_annotation_route';
}
copied to clipboard
PerferSafeSetState #
Prefer to check mounted before setState
class PerferSafeSetState extends DartLint {
@override
String get code => 'prefer_safe_setState';
}
copied to clipboard
MustCallSuperDispose #
Implementations of this method should end with a call to the inherited method, as in super.dispose().
class MustCallSuperDispose extends DartLint with CallSuperDisposeMixin {
@override
String get code => 'must_call_super_dispose';
}
copied to clipboard
EndCallSuperDispose #
Should call super.dispose() at the end of this method.
class EndCallSuperDispose extends DartLint with CallSuperDisposeMixin {
@override
String get code => 'end_call_super_dispose';
}
copied to clipboard
PerferDocComments #
https://dart.dev/guides/language/effective-dart/documentation
The same like public_member_api_docs, but we can ignore lint or ignore file by override [ignoreLint] and [ignoreFile] and you can override [isPrivate] and [inPrivateMember] to check private member.
class PerferDocComments extends DartLint {
@override
String get code => 'perfer_doc_comments';
}
copied to clipboard
PreferSingleton #
This is not a singleton, and new Object every time.
class PreferSingleton extends DartLint {
@override
String get code => 'prefer_singleton';
}
copied to clipboard
GoodDocComments #
wrong comments format. (/// xxx) for public api and (// xxx) for other cases.
class GoodDocComments extends DartLint {
@override
String get code => 'good_doc_comments';
}
copied to clipboard
PreferTrailingComma #
Prefer trailing comma for better code style.
class PreferTrailingComma extends DartLint {
@override
String get code => 'prefer_trailing_comma';
}
copied to clipboard
Completion #
Make a custom completion #
you can define your CompletionContributor in completionContributors, ExtensionMemberContributor is default.
/// The completionContributors to finish CompletionRequest
List<CompletionContributor> get completionContributors =>
<CompletionContributor>[
ExtensionMemberContributor(),
];
copied to clipboard
Suggestions of Extension Member #
Although dart team had close the issue Auto import (or quickfix?) for Extensions · Issue #38894 · dart-lang/sdk (github.com) , but still has many problems when developing in different ide.
ExtensionMemberContributor help to handle extension member easily.
Pre-Commit #
pre_commit.dart #
find pre_commit.dart base on following project tree, it's a demo to check errors before submit code.
├─ example
│ ├─ custom_lint
│ │ └─ tools
│ │ └─ analyzer_plugin
│ │ ├─ bin
│ │ │ └─ pre_commit.dart
copied to clipboard
pre-commit script #
run candies_analyzer_plugin --pre-commit, pre-commit script is generated under .git/hooks.
and you can modify this script template by add a pre-commit file under your project.
├─ example
│ ├─ pre-commit
copied to clipboard
{0} and {1} are placeholder, do not modify them.
#!/bin/sh
# project path
base_dir="{0}"
dart format "$base_dir"
# pre_commit.dart path
pre_commit="{1}"
echo "Checking the code before submit..."
echo "Analyzing $base_dir..."
info=$(dart "$pre_commit" "$base_dir")
echo "$info"
if [[ -n $info && $info != *"No issues found"* ]];then
exit 1
fi
copied to clipboard
when you are running git commit command to submit code, it will run under .git/hooks/pre-commit script first.
and the script will call example/custom_lint/tools/analyzer_plugin/bin/pre_commit.dart, if there are errors, you should exit 1.
you can modify example/pre-commit and example/custom_lint/tools/analyzer_plugin/bin/pre_commit.dart to custom your rule.
cacheErrorsIntoFile #
set CandiesAnalyzerPlugin.cacheErrorsIntoFile to true, to reduce the spent time to check error before submit code.
Note #
print lag #
don't write print in the process of analyzing in your plugin, analysis will lag.
pubspec.yaml and analysis_options.yaml #
you must do following things to support your project to be analyzed.
add custom_lint into dev_dependencies in pubspec.yaml , see pubspec.yaml
add custom_lint into analyzer plugins in analysis_options.yaml see analysis_options.yaml
quick fixes are only supported for dart files in vscode.(android studio support any type of file) #
issue
completion auto import is not working in vscode #
issue
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.