Tools to write automatic error fixes.
When creating a Lint.Rule.Error
, you can provide an automatic
fix for the error using Lint.Rule.withFixes
so that the
user doesn't need to fix the problem themselves.
In the CLI, the user can ask to fix the errors automatically, and in doing so, they will be presented by a fix which they can accept or refuse. If the fix gets refused, then the next fixable error will be presented. Otherwise, if the fix gets accepted, the file will be applied and the fixed file content get analyzed again by the different rules in the user's configuration, and then another fix will be presented. When there are no more fixable errors, the remaining errors will be reported, just like when the user doesn't request errors to be automatically fixed.
In summary, errors will be presented one by one and the user will validate them.
An automatic fix, when applied, should resolve the reported error completely. This means that when the automatic fix is applied, the user should not have to think about the error anymore or have to do additional work. Imagine if the user applies a lot of automatic fixes all at once, we don't want them to have to remember having to do something, otherwise we may have just offloaded a lot of work that they may forget to do. In that case, it is better not to provide a fix at all, so that they keep a reminder and the details of how to fix the problem.
An automatic fix should resolve only the reported error, not try to fix other potential errors. By only fixing one error at a time, the fix will be easier for the user to digest and understand. The file will be re-linted when the fix is applied, and then another error can fix that one.
For users, having an automatic fix always feels like a nice-to-have and they may request you to provide some, but they are not mandatory, and in some cases, it is better not to have any.
Sometimes, just by going through the whole file, you are missing some of the information needed to generate the right fix. Instead of providing a partial or potentially incorrect fix, it would be better to provide more details, hints or suggestions.
An automatic fix should not cause changes that would break the file or the project. In some cases, we can detect that the fix will break things, like if the result of the fix is invalid Elm code (as in resulting in a parsing error), but ultimately we can't detect that the project will still compile after the fix is applied.
Users are notified that an automatic fix is available. For performance reasons, we only check that a fix is valid before presenting it to the user and ignore it if it turns out to be invalid. This means that the user will be disappointed or confused when the error ends up not being enforced. The only way we have to prevent this is to write tests, as fixes are applied in tests.
Sometimes problems are learning opportunities, and it is worth having the user spend some time reading through the details of the error and trying several alternatives in order to understand the problem and the tradeoffs of the solutions. Do try to guide them by having great error details though!
The reasons to provide an automatic fix are basically the opposite of the reasons not to provide an automatic fix:
Automatic fixes are more error-prone than rules, especially since we may work with re-writing ports of the code, for which the AST does not provide the current formatting of a file (there is no information about spacing, line-breaks, ...). I suggest writing a lot of tests, and especially write tests where the formatting of the original file is a bit odd, as you may for instance unknowingly attempt to delete characters next to the thing you wanted to remove.
Fixes work with ranges or position. If the context of a different element is not available in the scope of where you create the error, then you should store it in the context of your rule.
Represents (part of a) fix that will be applied to a file's source code in order to automatically fix a linting error.
removeRange : Elm.Syntax.Range.Range -> Fix
Remove the code in between a range.
replaceRangeBy : Elm.Syntax.Range.Range -> String -> Fix
Replace the code in between a range by some other code.
insertAt : { row : Basics.Int, column : Basics.Int } -> String -> Fix
Insert some code at the given position.
Represents the result of having applied a list of fixes to a source code.
Represents a problem that may have occurred when attempting to apply a list of fixes.
fix : List Fix -> String -> FixResult
Apply the changes on the source code.