Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork3
The plugin to help create custom analyzer plugin quickly and provide some useful lints and get suggestion and auto import for extension member.
License
fluttercandies/candies_analyzer_plugin
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Languages: English |中文简体
The plugin to help create custom lint quickly.
- candies_analyzer_plugin
- simple use
- Custom your analyzer plugin
dev_dependencies:# zmtzawqlpcandies_analyzer_plugin:any
analyzer:# zmtzawqlpplugins:candies_analyzer_plugin
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)
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
intodev_dependencies
of the rootpubspec.yaml
dev_dependencies:# zmtzawqlpcustom_lint:path:custom_lint/
- add
custom_lint
intoanalyzer plugins
of the rootanalysis_options.yaml
analyzer:# zmtzawqlpplugins:custom_lint
after analysis are finished, you will see some custom lint in your ide.
findplugin.dart
base on following project tree
├─ example│ ├─ custom_lint│ │ └─ tools│ │ └─ analyzer_plugin│ │ ├─ bin│ │ │ └─ plugin.dart
plugin.dart
is the entrance of plugin.
we start plugin in this file.
CandiesAnalyzerPluginget plugin=>CustomLintPlugin();// This file must be 'plugin.dart'voidmain(List<String> args,SendPort sendPort) {CandiesAnalyzerPluginStarter.start( args, sendPort, plugin: plugin, );}classCustomLintPluginextendsCandiesAnalyzerPlugin {@overrideStringget name=>'custom_lint';@overrideList<String>get fileGlobsToAnalyze=>const<String>['**/*.dart','**/*.yaml','**/*.json', ];@overrideList<DartLint>get dartLints=><DartLint>[// add your dart lint herePerferCandiesClassPrefix(), ...super.dartLints, ];@overrideList<YamlLint>get yamlLints=><YamlLint>[RemoveDependency(package:'path')];@overrideList<GenericLint>get genericLints=><GenericLint>[RemoveDuplicateValue()];}
you just need to make a custom lint which extends fromDartLint
,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_ERROR COMPILE_TIME_ERROR HINT LINT STATIC_TYPE_WARNING STATIC_WARNING SYNTACTIC_ERROR TODO | The default is LINT. |
severity | The severity of the error. INFO WARNING ERROR | 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, seeissue |
you can ignore lint or ignore file by override [ignoreLint] and [ignoreFile].Here is a demo for a dart lint:
classPerferCandiesClassPrefixextendsDartLint {@overrideStringget code=>'perfer_candies_class_prefix';@overrideString?get url=>'https://github.com/fluttercandies/candies_analyzer_plugin';@overrideSyntacticEntity?matchLint(AstNode node) {if (nodeisClassDeclaration) {finalString name= node.name2.toString();finalint startIndex=_getClassNameStartIndex(name);if (!name.substring(startIndex).startsWith('Candies')) {return node.name2; } }returnnull; }@overrideStringget message=>'Define a class name start with Candies';@overrideFuture<List<SourceChange>>getDartFixes(DartAnalysisError error,CandiesAnalyzerPluginConfig config, )async {finalResolvedUnitResult resolvedUnitResult= error.result;finalAstNode astNode= error.astNode;// get name nodefinalToken nameNode= (astNodeasClassDeclaration).name2;finalString nameString= nameNode.toString();return<SourceChange>[awaitgetDartFix( resolvedUnitResult: resolvedUnitResult, message:'Use Candies as a class prefix.', buildDartFileEdit: (DartFileEditBuilder dartFileEditBuilder) {finalint startIndex=_getClassNameStartIndex(nameString);finalRegExp regExp=RegExp(nameString);finalString replace='${nameString.substring(0,startIndex)}Candies${nameString.substring(startIndex)}';for (finalMatch matchin 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; }}
Here is a demo for a yaml lint:
classRemoveDependencyextendsYamlLint {RemoveDependency({requiredthis.package});finalString package;@overrideStringget code=>'remove_${package}_dependency';@overrideStringget message=>'don\'t use $package!';@overrideString?get correction=>'Remove $package dependency';@overrideAnalysisErrorSeverityget severity=>AnalysisErrorSeverity.WARNING;@overrideIterable<SourceRange>matchLint(YamlNode root,String content,LineInfo lineInfo, )sync* {if (rootisYamlMap&& root.containsKey(PubspecField.DEPENDENCIES_FIELD)) {finalYamlNode dependencies= root.nodes[PubspecField.DEPENDENCIES_FIELD]!;if (dependenciesisYamlMap&& dependencies.containsKey(package)) {finalYamlNodeget= dependencies.nodes[package]!;int start= dependencies.span.start.offset;finalint end=get.span.start.offset;finalint index= content.substring(start, end).indexOf('$package: '); start+= index;yieldSourceRange(start,get.span.end.offset- start); } } }}
Here is a demo for a generic lint:
classRemoveDuplicateValueextendsGenericLint {@overrideStringget code=>'remove_duplicate_value';@overrideIterable<SourceRange>matchLint(String content,String file,LineInfo lineInfo, )sync* {if (isFileType(file: file, type:'.json')) {finalMap<dynamic,dynamic> map=jsonDecode(content)asMap<dynamic,dynamic>;finalMap<dynamic,dynamic> duplicate=<dynamic,dynamic>{};finalMap<dynamic,dynamic> checkDuplicate=<dynamic,dynamic>{};for (finaldynamic keyin map.keys) {finaldynamic value= map[key];if (checkDuplicate.containsKey(value)) { duplicate[key]= value; duplicate[checkDuplicate[value]]= value; } checkDuplicate[value]= key; }if (duplicate.isNotEmpty) {for (finaldynamic keyin duplicate.keys) {finalint start= content.indexOf('"$key"');finaldynamic value= duplicate[key];finalint end= content.indexOf('"$value"', start, )+ value.toString().length+1;finalint lineNumber= lineInfo.getLocation(end).lineNumber;bool hasComma=false;int commaIndex= end;int commaLineNumber= lineInfo.getLocation(commaIndex).lineNumber;while (!hasComma&& commaLineNumber== lineNumber) { commaIndex++;finalString char= content[commaIndex]; hasComma= char==','; commaLineNumber= lineInfo.getLocation(commaIndex).lineNumber; }yieldSourceRange(start, (hasComma? commaIndex: end)+1- start); } } } }@overrideStringget message=>'remove duplicate value';}
finddebug.dart
base on following project tree
├─ example│ ├─ custom_lint│ │ └─ tools│ │ └─ analyzer_plugin│ │ ├─ bin│ │ │ └─ debug.dart
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 {finalString root=Directory.current.parent.parent.parent.path;finalAnalysisContextCollection collection=AnalysisContextCollection(includedPaths:<String>[root]);finalCandiesAnalyzerPlugin myPlugin= plugin;for (finalAnalysisContext contextin collection.contexts) {for (finalString filein context.contextRoot.analyzedFiles()) {if (!myPlugin.shouldAnalyzeFile(file, context)) {continue; }finalbool isAnalyzed= context.contextRoot.isAnalyzed(file);if (!isAnalyzed) {continue; }finalList<AnalysisError> errors= (await myPlugin.getAnalysisErrorsForDebug( file, context, )) .toList();for (finalAnalysisError errorin errors) {finalList<AnalysisErrorFixes> fixes=await myPlugin .getAnalysisErrorFixesForDebug(EditGetFixesParams(file, error.location.offset), context) .toList();print(fixes.length); }print(errors.length); } }}
├─ example│ ├─ custom_lint│ │ └─ tools│ │ └─ analyzer_plugin
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 runcandies_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
so you should addcustom_lint
dependencies intoanalyzer_plugin\pubspec.yaml
you should useabsolute path
due to analyzer_plugin folder will copy to.plugin_manager
.
if your don't publishcustom_lint
as new package, i don't suggest do as this.
├─ example│ ├─ custom_lint│ │ ├─ lib│ │ │ └─ custom_lint.dart│ │ └─ tools│ │ └─ analyzer_plugin│ │ ├─ analysis_options.yaml
dependencies:custom_lint:# absolute pathpath:xxx/xxx/custom_lintcandies_analyzer_plugin:anypath:anyanalyzer:anyanalyzer_plugin:any
after update code, you should restart analysis server by following steps in vscode.
- find
Command Palette
inView
- enter
Restart Analysis Server
now, you can see the new change.
for performance, default is false, if you want to check log, set it to true. you can open Log.
CandiesAnalyzerPluginLogger().shouldLog = true;
Under the projectcustom_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, );
- log error
CandiesAnalyzerPluginLogger().logError('analyze file failed:', root: analysisContext.root, error: e, stackTrace: stackTrace, );
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
- exclude files
analyzer:exclude: -lib/exclude/*.dart
- disable a lint
linter:rules:# disable a lintperfer_candies_class_prefix:false
we can defineinclude
tag undercustom_lint
(it's your plugin name).it means that we only analyze the include files.
# your plugin namecustom_lint:# if we define this, we only analyze include filesinclude: -lib/include/*.dart
you can change lint severity by following setting.
change the severity ofperfer_candies_class_prefix
frominfo
towarning
.
supportwarning
,info
,error
.
analyzer:errors:# override error severityperfer_candies_class_prefix:warning
Define a class name start with prefix
classPerferClassPrefixextendsDartLint {PerferClassPrefix(this.prefix);finalString prefix;@overrideStringget code=>'perfer_${prefix}_class_prefix';}
Prefer to use asset const instead of a string.
classPreferAssetConstextendsDartLint {@overrideStringget code=>'prefer_asset_const';@overrideString?get url=>'https://pub.dev/packages/assets_generator'; }
Prefer to use named routes.
classPreferNamedRoutesextendsDartLint {@overrideStringget code=>'prefer_named_routes';@overrideString?get url=>'https://pub.dev/packages/ff_annotation_route'; }
Prefer to check mounted before setState
classPerferSafeSetStateextendsDartLint {@overrideStringget code=>'prefer_safe_setState';}
Implementations of this method should end with a call to the inherited method, as insuper.dispose()
.
classMustCallSuperDisposeextendsDartLintwithCallSuperDisposeMixin {@overrideStringget code=>'must_call_super_dispose';}
Should callsuper.dispose()
at the end of this method.
classEndCallSuperDisposeextendsDartLintwithCallSuperDisposeMixin {@overrideStringget code=>'end_call_super_dispose';}
https://dart.dev/guides/language/effective-dart/documentationThe same likepublic_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.
classPerferDocCommentsextendsDartLint {@overrideStringget code=>'perfer_doc_comments';}
This is not a singleton, and new Object every time.
classPreferSingletonextendsDartLint {@overrideStringget code=>'prefer_singleton';}
wrong comments format. (/// xxx) for public api and (// xxx) for other cases.
classGoodDocCommentsextendsDartLint {@overrideStringget code=>'good_doc_comments';}
Prefer trailing comma for better code style.
classPreferTrailingCommaextendsDartLint {@overrideStringget code=>'prefer_trailing_comma';}
you can define yourCompletionContributor
incompletionContributors
,ExtensionMemberContributor
is default.
/// The completionContributors to finish CompletionRequestList<CompletionContributor>get completionContributors=><CompletionContributor>[ExtensionMemberContributor(), ];
Although dart team had close the issueAuto 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.
findpre_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
runcandies_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
{0} and {1} are placeholder, do not modify them.
#!/bin/sh# project pathbase_dir="{0}"dart format "$base_dir"# pre_commit.dart pathpre_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"* ]];thenexit 1fi
when you are runninggit commit
command to submit code, it will run under .git/hooks/pre-commit script first.and the script will callexample/custom_lint/tools/analyzer_plugin/bin/pre_commit.dart
, if there are errors, you should exit 1.
you can modifyexample/pre-commit
andexample/custom_lint/tools/analyzer_plugin/bin/pre_commit.dart
to custom your rule.
setCandiesAnalyzerPlugin.cacheErrorsIntoFile
to true, to reduce the spent time to check error before submit code.
don't writeprint
in the process of analyzing in your plugin, analysis will lag.
you must do following things to support your project to be analyzed.
add
custom_lint
intodev_dependencies
inpubspec.yaml
, seepubspec.yamladd
custom_lint
intoanalyzer
plugins
inanalysis_options.yaml
seeanalysis_options.yaml
About
The plugin to help create custom analyzer plugin quickly and provide some useful lints and get suggestion and auto import for extension member.
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.