-
Notifications
You must be signed in to change notification settings - Fork 357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add a --fix option to auto fix small issues like eslint #266
Comments
I'm potentially interested in this, quite a bit of ground work involved in making this possible though.
However, it does strike me that idea of a directory as a module in Terraform as opposed to a file as a module in JS does seem to make this a bunch harder. ESLint can build an AST for a file, let each rule mutate it, and then write the final result back to the file. TFLint rules (via the runner) work at a module level, and only recently have been able to perform raw HCL decoding (#745). All of this is based on the idea that the configuration is immutable. Once a write is performed, the representation TFLint has of the module is invalid and everything has to be reloaded from disk. That's a bit easier if we're willing to write changes after each rule, but that's really not ideal as it would tend to close off a So as much as I'd be interested I'm not sure I'll have the time to devote to getting this off the ground. |
I'm currently looking into ways to introduce autofix. Here are some proofs of concept.
I would like to share the design I have in mind at the moment. The first thing that bothered me was how to design the SDK API. The most appropriate way to process HCL files is to expose the
https://pkg.go.dev/github.com/hashicorp/hcl/v2@v2.16.2/hclwrite However, since plugin developers determine whether there is an issue based on the The solution to this issue is to provide a direct file editing API based on the type Fixer struct {
sources map[string][]byte
}
func (f *Fixer) Replace(rng hcl.Range, replaced string) {
file := f.sources[rng.Filename]
buf := bytes.NewBuffer(file[:rng.Start.Byte])
buf.WriteString(replaced)
buf.Write(file[rng.End.Byte:])
f.sources[rng.Filename] = buf.Bytes()
} Other APIs such as text deletion and addition can be implemented in the
https://eslint.org/docs/latest/extend/custom-rules#applying-fixes It's difficult to easily implement advanced APIs like those provided by The runner.EmitIssueWithFix(
rule,
"Single line comments should begin with #",
token.Range,
func (f *Fixer) error { return f.Replace(rangeForPrefix, "#") }
) By providing it as part of The modified source code is sent from the plugin to the host over gRPC. These are stored in memory as a field of the Lines 43 to 74 in 9b802b1
Rebuilding However, if we take this approach, we lose the possibility of parallel execution. At the moment, the calls from the plugin are executed serially, so this way is not a problem, but if we want parallel execution for performance reasons, we may need to reconsider this approach. Also, if a module call is added by autofix, module inspection is not performed on it. This is an implementation limitation, but I believe it's a very rare case and shouldn't be a big concern. The accumulated changes are finally written out to the filesystem after outputting issues. At this time, there is room for discussion as to which issue should be output. ESLint does not output autofixed problems, only the remaining problems.
https://eslint.org/docs/latest/use/command-line-interface#fix-problems On the other hand, RuboCop, a famous linter for Ruby, also outputs fixed offenses. ESLint's behavior is nice to use with pre-commit, while RuboCop is better for confirming which issues were actually fixed. Personally, I think RuboCop's behavior is better. However, there is a problem with what point in time the output of the fixed issue refers to the source code. For example, imagine code like this: Revision 1 variable "unused" {}
// comment Suppose the autofix by Revision 2 // comment Suppose the autofix by Revision 3 # comment For the Finally, we also have to think about conflicting autofixes. Consider the following example: // comment Suppose the autofix by # comment Suppose an autofix that adds resource definitions with a comment is applied. # comment
// This is added by autofix
resource "aws_instance" "foo" {} Note that the comment added by the second rule does not have the EDIT: ESLint also uses this strategy. |
Providing multiple methods (e.g., RuboCop's strategy of re-evaluating modified files and detecting loops between rules seems like a good approach for ensuring that behavior is deterministic and doesn't depend on rule execution order. |
For example, we could fix
aws_iam_policy_document
'sactions
jsonencode()
to dump//
comments with#
The text was updated successfully, but these errors were encountered: