# Adding Quick Fixes ## Introduction When your detector reports an incident, it can also provide one or more "quick fixes“, which are actions the users can invoke in the IDE (or, for safe fixes, in batch mode) to address the reported incident. For example, if the lint check reports an unused resource, a quick fix could offer to remove the unused resource. In some cases, quick fixes can take partial steps towards fixing the problem, but not fully. For example, the accessibility lint check which makes sure that for images you set a content description, the quickfix can offer to add it -- but obviously it doesn't know what description to put. In that case, the lint fix will go ahead and add the attribute declaration with the correct namespace and attribute name, but will leave the value up to the user (so it uses a special quick fix provided by lint to place a TODO marker as the value, along with selecting just that TODO string such that the user can type to replace without having to manually delete the TODO string first.) ## The LintFix builder class The class in lint which represents a quick fix is `LintFix`. Note that `LintFix` is **not** a class you can subclass and then for example implement your own arbitrary code in something like a `perform()` method. Instead, `LintFix` has a number of builders where you *describe* the action that you would like the quickfix to take. Then, lint will offer that quickfix in the IDE, and when the user invokes it, lint runs its own implementation of the various descriptors. The historical reason for this is that many of the quickfixes in lint depended on machinery in the IDE (such as code and import cleanup after an edit operation) that isn't available in lint itself, along with other concepts that only make sense in the IDE, such as moving the caret, opening files, selecting text, and so on. More recently, this is also used to persist quickfixes properly for later reuse; this is required for [partial analysis](partial-analysis.md.html). ## Creating a LintFix Lint fixes use a ”fluent API“; you first construct a `LintFix`, and on that method you call various available type methods, which will then further direct you to the allowed options. For example, to create a lint fix to set an XML attribute of a given name to ”true“, use something like this: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin LintFix fix = fix().set(null, "singleLine", "true").build() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here the `fix()` method is provided by the `Detector` super class, but that's just a utility method for `LintFix.fix()` (or in older versions, `LintFix.create()`). There are a number of additional, common methods you can set on the `fix()` object: * `name`: Sets the description of the lint fix. This should be brief; it's in the quickfix popup shown to the user. * `sharedName`: This sets the ”shared“ or ”family“ name: all fixes in the file will with the same name can be applied in a single invocation by the user. For example, if you register 500 ”Remove unused import“ quickfixes in a file, you don't want to force the user to have to invoke each and every one. By setting the shared name, the user will be offered to **Fix All *$family name* problems in the current file**, which they can then perform to have all 500 individual fixes applied in one go. * `autoFix`: If you get a lint report and you notice there are a lot of incidents that lint can fix automatically, you don't want to have to go and open each and every file and all the fixes in the file. Therefore, lint can apply the fixes in batch mode; the Gradle integration has a `lintFix` target to perform this, and the `lint` command has an `--apply-suggestions` option. However, many quick fixes require user intervention. Not just the ones where the user has to choose among alternatives, and not just the ones where the quick fix inserts a placeholder value like TODO. Take for example lint's built-in check which requires overrides of a method annotated with `@CallSuper` to invoke `super.` on the overridden method. Where should we insert the call -- at the beginning? At the end? Therefore, lint has the `autoFix` property you can set on a quickfix. This indicates that this fix is ”safe“ and can be performed in batch mode. When the `lintFix` target runs, it will only apply fixes marked safe in this way. ## Available Fixes The current set of available quick fix types are: * `fix().replace`: String replacements. This is the most general mechanism, and allows you to perform arbitrary edits to the source code. In addition to the obvious ”replace old string with new“, the old string can use a different location range than the incident range, you can match with regular expressions (and perform replacements on a specific group within the regular expression), and so on. This fix is also the most straightforward way to **delete** text. It offers some useful cleanup operations: - Source code cleanup, which will run the IDE's code formatter on the modified source code range. This will apply the user's code preferences, such as whether there should be a space between a cast and the expression, and so on. - Import cleanup. That means that if you are referencing a new type, you don't have to worry about checking whether it is imported and if not adding an import statement; you can simply write your string replacements using the fully qualified names, and then tag the quickfix with the import cleanup option, and when the quickfix is performed the import will be added if necessary and all the fully qualified references replaced with simple names. And this will also correctly handle the scenario where the symbols cannot be replaced with simple names because there is a conflicting import of the same name from a different package. Normally, you should write your replacement source code using fully qualified names, and then apply `shortenNames` to the quickfix to tell lint to replace fully qualified names with imports; don't try to write your quickfix to also add the import statements on its own. There's a possibility that a given name cannot be imported because it's already importing the same name for a different namespace. When using fully qualified names, lint will specifically handle this. In some cases you cannot use fully qualified names in the code snippet; this is the case with Kotlin extension functions for example. For that scenario, the replacement quickfix has an `imports` property you can use to specify methods (and classes and fields) to import at the same time. * `fix().annotate`: Annotating an element. This will add (or optionally replace) an annotation on a source element such as a method. It will also handle import management. * `fix().set`: Add XML attributes. This will insert an attribute into the given element, applying the user's code style preferences for where to insert the attribute. (In Android XML for example there's a specific sorting convention which is generally alphabetical, except layout params go before other attributes, and width goes before height.) You can either set the value to something specific, or place the caret inside the newly created empty attribute value, or set it to TODO and select that text for easy type-to-replace. !!! Tip If you use the `todo()` quickfix, it's a good idea to special case your lint check to deliberately not accept ”TODO“ as a valid value. For example, for lint's accessibility check which makes sure you set a content description, it will complain both when you haven't set the content description attribute, **and** if the text is set to ”TODO“. That way, if the user applies the quickfix, which creates the attribute in the right place and moves the focus to the right place, the editor is still showing a warning that the content description should be set. * `fix().unset`: Remove XML attribute. This is a special case of add attribute. * `fix().url`: Show URL. In some cases, you can't ”fix“ or do anything local to address the problem, but you really want to direct the user's attention to additional documentation. In that case, you can attach a ”show this URL“ quick fix to the incident which will open the browser with the given URL when invoked. For example, in a complicated deprecation where you want users to migrate from one approach to a completely different one that you cannot automate, you could use something like this: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin val message = "Job scheduling with `GcmNetworkManager` is deprecated: Use AndroidX `WorkManager` instead" val fix = fix() .url("https://developer.android.com/topic/libraries/architecture/workmanager/migrating-gcm") .build() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ## Combining Fixes You might notice that lint's APIs to report incidents only takes a **single** quick fix instead of a list of fixes. But let's say that it *did* take a list of quick fixes. - Should they *all* be performed as a single unit? That makes sense if you're trying to write a quickfix which performs multiple string replacements. - Or should they be offered as separate alternatives for the user to choose between? That makes sense if the incident says for example that you must set at least one attribute among three possibilities; in this case we may want to add quickfixes for setting each attribute. Both scenarios have their uses, so lint makes this explicit: - `fix().composite`: create a ”composite“ fix, which composes the fix out of multiple individual fixes, or - `fix().alternatives`: create an ”alternatives“ fix, which holds a number of individual fixes, which lint will present as separate options to the user. Here's an example of how to create a composite fix, which will be performed as a unit; here we're both setting a new attribute and deleting a previous attribute: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin val fix = fix().name("Replace with singleLine=\"true\"") .composite( fix().set(ANDROID_URI, "singleLine", "true").build(), fix().unset(namespace, oldAttributeName).build() ) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ And here's an example of how to create an alternatives fix, which are offered to the user as separate options; this is from our earlier example of the accessibility check which requires you to set a content description, which can be set either on the ”text“ attribute or the "contentDescription” attribute: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin val fix = fix().alternatives( fix().set().todo(ANDROID_URI, "text").build(), fix().set().todo(ANDROID_URI, "contentDescription") .build()) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ## Refactoring Java and Kotlin code It would be nice if there was an AST manipulation API, similar to UAST for visiting ASTs, that quickfixes could use to implement refactorings, but we don't have a library like that. And it's unlikely it would work well; when you rewrite the user's code you typically have to take language specific conventions into account. Therefore, today, when you create quickfixes for Kotlin and Java code, if the quickfix isn't something simple which would work for both languages, then you need to conditionally create either the Kotlin version or the Java version of the quickfix based on whether the source file it applies to is in Kotlin or Java. (For an easy way to check you can use the `isKotlin` or `isJava` package level methods in `com.android.tools.lint.detector.api`.) However, it's often the case that the quickfix is something simple which would work for both; that's true for most of the built-in lint checks with quickfixes for Kotlin and Java. ## Regular Expressions and Back References The `replace` string quick fix allows you to match the text to with regular expressions. You can also use back references in the regular expression such that the quick fix replacement text includes portions from the original string. Here's an example from lint's `AssertDetector`: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers val fix = fix().name("Surround with desiredAssertionStatus() check") .replace() .range(context.getLocation(assertCall)) .pattern("(.*)") .with("if (javaClass.desiredAssertionStatus()) { \\k<1> }") .reformat(true) .build() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The replacement string's back reference above, on line 5, is \k<1>. If there were multiple regular expression groups in the replacement string, this could have been \k<2>, \k<3>, and so on. Here's how this looks when applied, from its unit test: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin lint().files().run().expectFixDiffs( """ Fix for src/test/pkg/AssertTest.kt line 18: Surround with desiredAssertionStatus() check: @@ -18 +18 - assert(expensive()) // WARN + if (javaClass.desiredAssertionStatus()) { assert(expensive()) } // WARN """ ) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ## Emitting quick fix XML to apply on CI Note that the `lint` has an option (`--describe-suggestions`) to emit an XML file which describes all the edits to perform on documents to apply a fix. This maps all quick fixes into chapter edits (including XML logic operations). This can be (and is, within Google) used to integrate with code review tools such that the user can choose whether to auto-fix a suggestion right from within the code review tool.