diff --git a/lint/go.mod b/lint/go.mod index 31fd0a42..7a741194 100644 --- a/lint/go.mod +++ b/lint/go.mod @@ -6,9 +6,9 @@ toolchain go1.22.3 require ( github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/onflow/cadence v1.2.1 + github.com/onflow/cadence v1.3.0 github.com/onflow/flow-go-sdk v1.2.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.63.2 ) @@ -29,7 +29,7 @@ require ( github.com/logrusorgru/aurora/v4 v4.0.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/onflow/atree v0.8.0 // indirect + github.com/onflow/atree v0.8.1 // indirect github.com/onflow/crypto v0.25.1 // indirect github.com/onflow/flow/protobuf/go/flow v0.4.7 // indirect github.com/onflow/go-ethereum v1.13.4 // indirect @@ -40,13 +40,13 @@ require ( github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/otel v1.24.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gonum.org/v1/gonum v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/lint/go.sum b/lint/go.sum index f7695715..ba2a741e 100644 --- a/lint/go.sum +++ b/lint/go.sum @@ -23,7 +23,6 @@ github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQ github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -41,10 +40,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/onflow/atree v0.8.0 h1:qg5c6J1gVDNObughpEeWm8oxqhPGdEyGrda121GM4u0= -github.com/onflow/atree v0.8.0/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= -github.com/onflow/cadence v1.2.1 h1:hmSsgX3rTsp2E5qTSl1JXINt8qepdRrHTwDSYqN5Nxs= -github.com/onflow/cadence v1.2.1/go.mod h1:fJxxOAp1wnWDfOHT8GOc1ypsU0RR5E3z51AhG8Yf5jg= +github.com/onflow/atree v0.8.1 h1:DAnPnL9/Ks3LaAnkQVokokTBG/znTW0DJfovDtJDhLI= +github.com/onflow/atree v0.8.1/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/cadence v1.3.0 h1:COTlTqUACtTvOeFe7+jP9UDVEU3M3OZzrbzzsEbyqCk= +github.com/onflow/cadence v1.3.0/go.mod h1:638c9Zy25EwflSEE7tBFAVM9N6uwcWt77sgKpyYfSTc= github.com/onflow/crypto v0.25.1 h1:0txy2PKPMM873JbpxQNbJmuOJtD56bfs48RQfm0ts5A= github.com/onflow/crypto v0.25.1/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/flow-go-sdk v1.2.2 h1:F78Sq/VaExgtaQv739k06gnx2aIyLF5wVE0XwxFpmsc= @@ -67,44 +66,45 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d h1:5JInRQbk5UBX8JfUvKh2oYTLMVwj3p6n+wapDDm7hko= github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d/go.mod h1:Nlx5Y115XQvNcIdIy7dZXaNSUpzwBSge4/Ivk93/Yog= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= diff --git a/lint/ineffassign_analyzer.go b/lint/ineffassign_analyzer.go new file mode 100644 index 00000000..38630f63 --- /dev/null +++ b/lint/ineffassign_analyzer.go @@ -0,0 +1,461 @@ +/* + * Cadence lint - The Cadence linter + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lint + +import ( + "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/sema" + "github.com/onflow/cadence/tools/analysis" + "slices" +) + +type operationKind int + +const ( + operationKindAssign = iota + operationKindPartialAssign + operationKindUse +) + +type warning struct { + el ast.Element + message string +} + +type analyzeResult struct { + warnings []warning +} + +func (r *analyzeResult) add(el ast.Element, message string) { + w := warning{ + el: el, + message: message, + } + if !slices.Contains(r.warnings, w) { + r.warnings = append(r.warnings, w) + } +} + +func (r *analyzeResult) merge(r2 *analyzeResult) { + for _, w := range r2.warnings { + r.add(w.el, w.message) + } +} + +type variable struct { + name string + declaration ast.Element + functionBlock *block +} + +type operations []*operation + +func (ops operations) uniqueVariables() []*variable { + variables := make([]*variable, 0) + for _, op := range ops { + if !slices.Contains(variables, op.variable) { + variables = append(variables, op.variable) + } + } + return variables +} + +func (ops operations) filterByVariable(v *variable) operations { + res := make(operations, 0, len(ops)) + for _, op := range ops { + if op.variable.declaration != v.declaration { + continue + } + res = append(res, op) + } + return res +} + +type operation struct { + variable *variable + kind operationKind + el ast.Element +} + +type block struct { + parent *block + children []*block + locals map[string]*variable + operations operations +} + +func newOperation(kind operationKind, variable *variable, el ast.Element) *operation { + return &operation{ + variable: variable, + kind: kind, + el: el, + } +} + +func newBlock(parents ...*block) *block { + bl := &block{ + locals: make(map[string]*variable), + operations: make(operations, 0), + children: make([]*block, 0), + } + + for _, parent := range parents { + parent.addChild(bl) + } + return bl +} + +func (b *block) addChild(child *block) { + b.children = append(b.children, child) + child.parent = b +} + +func (b *block) variable(name string) *variable { + + if v, ok := b.locals[name]; ok { + return v + } + if b.parent == nil { + return nil + } + return b.parent.variable(name) +} + +func (b *block) declare(name string, el ast.Element) *variable { + b.locals[name] = &variable{ + name: name, + declaration: el, + functionBlock: newBlock(), + } + b.assign(name, el) + return b.locals[name] +} + +func (b *block) assign(name string, el ast.Element) { + v := b.variable(name) + if v != nil { + b.operations = append(b.operations, newOperation(operationKindAssign, v, el)) + } +} + +func (b *block) partialAssign(name string, el ast.Element) { + v := b.variable(name) + if v != nil { + b.operations = append(b.operations, newOperation(operationKindPartialAssign, v, el)) + } +} + +func (b *block) useElement(el ast.Element) { + if el == nil { + return + } + switch e := el.(type) { + case *ast.IdentifierExpression: + v := b.variable(e.Identifier.Identifier) + if v != nil { + b.operations = append(b.operations, newOperation(operationKindUse, v, nil)) + } + } + el.Walk(b.useElement) +} + +func Walk(pass *analysis.Pass, bl *block, el ast.Element) { + + program := pass.Program + elaboration := program.Checker.Elaboration + var walk func(el ast.Element) + b := bl + + walk = func(element ast.Element) { + if element == nil { + return + } + + switch e := element.(type) { + + case *ast.IdentifierExpression: + b.useElement(e) + + case *ast.FunctionDeclaration: + v := b.declare(e.Identifier.Identifier, e) + v.functionBlock = newBlock(b) + Walk(pass, v.functionBlock, e.FunctionBlock) + v.functionBlock.parent = nil + return + + case *ast.VariableDeclaration: + identifier := e.Identifier.Identifier + + // resource tracking handles those cases no need to track the variable + // just track uses on values ( function parameters etc ) + if e.Transfer.Operation == ast.TransferOperationMove { + walk(e.Value) + if e.SecondValue != nil { + walk(e.SecondValue) + } + return + } + + variableType := elaboration.VariableDeclarationTypes(e) + if variableType.TargetType.Tag().Equals(sema.FunctionTypeTag) { + v := b.declare(identifier, e) + v.functionBlock = newBlock(b) + walk(e.Value) + v.functionBlock.parent = nil + return + } + + // mark locals used in value expression as used + walk(e.Value) + + // track identifier if it is not a reference + if !variableType.TargetType.IsOrContainsReferenceType() { + b.declare(identifier, e) + } + return + + case *ast.AssignmentStatement: + + // no need to track resources, just track locals used on RHS + if e.Transfer.Operation == ast.TransferOperationMove { + walk(e.Value) + return + } + + switch target := e.Target.(type) { + case *ast.IdentifierExpression: + identifier := target.Identifier.Identifier + variableType := elaboration.AssignmentStatementTypes(e) + if variableType.TargetType.Tag().Equals(sema.FunctionTypeTag) { + v := b.variable(identifier) + v.functionBlock = newBlock(b) + walk(e.Value) + v.functionBlock.parent = nil + return + } + walk(e.Value) + b.assign(target.Identifier.Identifier, e) + + case *ast.IndexExpression: + walk(e.Value) + b.partialAssign(target.TargetExpression.String(), e) + + default: + walk(e.Value) + } + return + + case *ast.InvocationExpression: + walk(e.InvokedExpression) + for _, arg := range e.Arguments { + walk(arg.Expression) + } + + target, ok := e.InvokedExpression.(*ast.IdentifierExpression) + if !ok { + return + } + + identifier := target.Identifier.Identifier + v := b.variable(identifier) + if v != nil { + for _, op := range v.functionBlock.operations { + b.operations = append(b.operations, op) + } + } + + case *ast.ReturnStatement: + walk(e.Expression) + + case *ast.IfStatement: + walk(e.Test) + + thenBlock := newBlock(b) + elseBlock := newBlock(b) + contBlock := newBlock(thenBlock, elseBlock) + + Walk(pass, thenBlock, e.Then) + if e.Else != nil { + Walk(pass, elseBlock, e.Else) + } + b = contBlock + + case *ast.WhileStatement: + b.useElement(e.Block) + b.useElement(e.Test) + + case *ast.ForStatement: + b.useElement(e.Block) + b.useElement(e.Value) + + case *ast.SwitchStatement: + walk(e.Expression) + for _, swCase := range e.Cases { + walk(swCase.Expression) + for _, s := range swCase.Statements { + b.useElement(s) + } + } + + default: + e.Walk(walk) + } + + } + + if el != nil { + walk(el) + } +} + +func (b *block) checkConsecutiveAssignmentsAndUnused(ops operations) *analyzeResult { + result := &analyzeResult{} + + var lastWrite ast.Element = nil + + for _, v := range ops.uniqueVariables() { + vOps := ops.filterByVariable(v) + + for i := 0; i < len(vOps); i++ { + currentOp := vOps[i] + if v.declaration != currentOp.variable.declaration { + continue + } + + switch currentOp.kind { + case operationKindAssign, operationKindPartialAssign: + lastWrite = currentOp.el + if i < len(vOps)-1 { + nextOp := vOps[i+1] + if nextOp.kind == operationKindAssign { + if len(b.children) == 0 { + result.add(currentOp.el, "ineffectual assign") + } + } + } + + case operationKindUse: + lastWrite = nil + + } + } + + if lastWrite != nil { + if len(b.children) == 0 { + result.add(lastWrite, "unused assign") + } + } + } + + return result +} + +func (b *block) getBestChildWarnings(ops operations) *analyzeResult { + if len(b.children) == 0 { + return &analyzeResult{warnings: []warning{}} + } + + bestResult := b.children[0].check(ops) + bestCount := len(bestResult.warnings) + + for _, child := range b.children[1:] { + childResult := child.check(ops) + childWarnings := len(childResult.warnings) + + if childWarnings < bestCount { + bestResult = childResult + bestCount = childWarnings + } else if childWarnings == bestCount { + bestResult.merge(childResult) + } + } + + return bestResult +} + +func (b *block) check(baseOps operations) *analyzeResult { + ops := append(baseOps, b.operations...) + result := b.checkConsecutiveAssignmentsAndUnused(ops) + childResult := b.getBestChildWarnings(ops) + result.merge(childResult) + return result +} + +var IneffAssignAnalyzer = (func() *analysis.Analyzer { + + elementFilter := []ast.Element{ + (*ast.Block)(nil), + (*ast.SwitchStatement)(nil), + } + + return &analysis.Analyzer{ + Description: "Detects ineffectual & unused assignments", + Requires: []*analysis.Analyzer{ + analysis.InspectorAnalyzer, + }, + Run: func(pass *analysis.Pass) interface{} { + inspector := pass.ResultOf[analysis.InspectorAnalyzer].(*ast.Inspector) + + reportBlock := func(b *block) { + seen := operations{} + for _, warning := range b.check(seen).warnings { + pass.Report( + analysis.Diagnostic{ + Location: pass.Program.Location, + Range: ast.NewRangeFromPositioned(nil, warning.el), + Category: RemovalCategory, + Message: warning.message, + }, + ) + } + } + inspector.Preorder( + elementFilter, + func(element ast.Element) { + switch el := element.(type) { + case *ast.Block: + b := newBlock() + Walk(pass, b, el) + reportBlock(b) + case *ast.SwitchStatement: + for _, swCase := range el.Cases { + b := newBlock() + for _, s := range swCase.Statements { + Walk(pass, b, s) + } + reportBlock(b) + } + + } + + }, + ) + + return nil + }, + } + +})() + +func init() { + RegisterAnalyzer( + "ineff-assign", + IneffAssignAnalyzer, + ) +}