Some general information about writing inspection, intention and quick fixes available in Code Inspections document.
It's important to know about PSI.
Some PSI viewer will be extremely useful. It might be built-in Psi Viewer that is going to be enabled when testing inspections with IDEA
run configuration (idea.is.internal=true must be set to active it in Kotlin project). Alternately, external plugin PsiViewer can be installed.
Each inspection should be registered in plugin-common.xml
. This file is included to plugin.xml
for all plugins obtained from Kotlin source code.
Each inspection should be added with the description. The description file should be named after correspondent inspection class with adding *.html
extension and removing Inspection
suffix. (Some.html
for inspection class SomeInspection
).
All inspections should have automatic tests. A default place for such tests is inspectionsLocal
group (inspections
group can be used if there are no quick fixes available for the inspection).
ProblemHighlightType
should always be ProblemHighlightType.GENERIC_ERROR_OR_WARNING
or empty (there‘s a correspondent overload for ProblemsHolder.registerProblem()
), otherwise, it won’t be possible to individually change the desired level in the inspection settings.
Inspection highlighting range shouldn‘t be too wide. For example, to show some problem in a class it’s much better to highlight its name than to highlight the whole class (the latter just looks very nasty).
Resolve operations (analyze
, resolveToCall
, resolveToDescriptors
) are considered to be expensive and shouldn‘t be triggered more often than it’s absolutely needed. All possible checks should be applied on PSI or file text before actual resolve.
resolveToDescriptorIfAny()
/ resolveToCall()
functions can be used if the single descriptor or resolved call is needed. It's better to call analyze
for obtaining BindingTrace
and use it for fetching other resolution results when inspection requires multiple resolve calls in a row.
Prefer resolveToDescriptorIfAny()
over resolveToDesciptor()
because of exceptions that are thrown from latter function when descriptor is absent.
Any checks about code state and resolve that were made during reporting an inspection problem an registering a quick fix might be invalidated by the moment of the actual quick fix execution. Avoid the code that can throw exceptions because of that. Re-checks with early exit from the quick fix can be used to workaround it.
Intentions and quick fixes execution happens in the UI thread so do not call long operations such as usages search or resolve to avoid freezes. PSI
elements obtained from resolve during the applicability check can be stored in SmartPsiElementPointer
for the postponed modification in quick-fixes. All complex searches should be executed in a background thread under a progress indicator. Some tests already assert that resolve operations are not called from UI thread while applying fixes.
There shouldn't be PSI elements stored in QuickFix classes (val psi: PsiElement
) as such elements might be invalidated and can lead to memory leaks. Smart pointer (check SmartPsiElementPointer
class and createSmartPointer()
function) can be used when such storage is absolutely necessary.
It's possible to obtain LanguageVersionSettings
directly from psiElement
with languageVersionSettings
extension.
Kotlin project itself can be used for new inspection testing. Build a test plugin with the new inspection included, open another instance of Kotlin project and run inspection on the whole project (Run Inspection By Name
action).