-
Notifications
You must be signed in to change notification settings - Fork 48
Add an inspection that reports properties being set to their default value #169
Changes from all commits
060121a
6d6b155
073bba9
dd2edf8
038d4a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<html> | ||
<body> | ||
Reports properties that are set to their default value | ||
<p> | ||
<!-- tooltip end --> | ||
<p> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,6 @@ import ( | |
"fmt" | ||
"os" | ||
"path/filepath" | ||
"reflect" | ||
"time" | ||
) | ||
|
||
|
@@ -127,8 +126,11 @@ func export(v *schema.Schema) SchemaDefinition { | |
} | ||
|
||
// TODO: Find better solution | ||
if defValue, err := v.DefaultValue(); err == nil && defValue != nil && !reflect.DeepEqual(defValue, v.Default) { | ||
item.Default = exportValue(defValue, fmt.Sprintf("%T", defValue)) | ||
if defValue, err := v.DefaultValue(); err == nil && defValue != nil { | ||
defValueAsMap, ok := defValue.(map[string]interface{}) | ||
if !ok || len(defValueAsMap) != 0 { | ||
item.Default = exportValue(defValue, fmt.Sprintf("%T", defValue)) | ||
} | ||
} | ||
return item | ||
} | ||
|
@@ -151,10 +153,12 @@ func exportValue(value interface{}, t string) *SchemaElement { | |
} | ||
vt, ok := value.(schema.ValueType) | ||
if ok { | ||
return &SchemaElement{Value: shortenType(fmt.Sprintf("%v", vt))} | ||
valStr := shortenType(fmt.Sprintf("%v", vt)) | ||
return &SchemaElement{Value: &valStr} | ||
} | ||
// Unknown case | ||
return &SchemaElement{Type: t, Value: fmt.Sprintf("%v", value)} | ||
valStr := fmt.Sprintf("%v", value) | ||
return &SchemaElement{Type: t, Value: &valStr} | ||
} | ||
|
||
func Generate(provider *schema.Provider, name string, outputPath string) { | ||
|
@@ -190,13 +194,39 @@ func DoGenerate(provider *schema.Provider, providerName string, outputFilePath s | |
|
||
type SchemaElement struct { | ||
// One of "schema.ValueType" or "SchemaElements" or "SchemaInfo" | ||
Type string `json:",omitempty"` | ||
Type string | ||
// Set for simple types (from ValueType) | ||
Value string `json:",omitempty"` | ||
Value *string | ||
// Set if Type == "SchemaElements" | ||
ElementsType string `json:",omitempty"` | ||
ElementsType string | ||
// Set if Type == "SchemaInfo" | ||
Info SchemaInfo `json:",omitempty"` | ||
Info SchemaInfo | ||
} | ||
|
||
func (e *SchemaElement) MarshalJSON() ([]byte, error) { | ||
if e.Value == nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe there is a better way? I don't know much about Go. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neither do I :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Origin
Should do the trick. You don't need to specify separate Marshaller for this. And I don't understand why original |
||
return json.Marshal(&struct { | ||
Type string `json:",omitempty"` | ||
ElementsType string `json:",omitempty"` | ||
Info SchemaInfo `json:",omitempty"` | ||
}{ | ||
Type: e.Type, | ||
ElementsType: e.ElementsType, | ||
Info: e.Info, | ||
}) | ||
} else { | ||
return json.Marshal(&struct { | ||
Type string `json:",omitempty"` | ||
Value string | ||
ElementsType string `json:",omitempty"` | ||
Info SchemaInfo `json:",omitempty"` | ||
}{ | ||
Type: e.Type, | ||
Value: *e.Value, | ||
ElementsType: e.ElementsType, | ||
Info: e.Info, | ||
}) | ||
} | ||
} | ||
|
||
type SchemaDefinition struct { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright 2000-2017 JetBrains s.r.o. | ||
* | ||
* 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 org.intellij.plugins.hcl.terraform.config.inspection | ||
|
||
import com.intellij.codeInsight.intention.LowPriorityAction | ||
import com.intellij.codeInspection.* | ||
import com.intellij.openapi.application.ApplicationManager | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.psi.PsiElementVisitor | ||
import org.intellij.plugins.hcl.psi.* | ||
import org.intellij.plugins.hcl.terraform.config.TerraformFileType | ||
import org.intellij.plugins.hcl.terraform.config.codeinsight.ModelHelper | ||
|
||
class TFDefaultPropertyInspection : LocalInspectionTool() { | ||
|
||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { | ||
return if (holder.file.fileType == TerraformFileType) MyEV(holder) else super.buildVisitor(holder, isOnTheFly) | ||
} | ||
|
||
inner class MyEV(val holder: ProblemsHolder) : HCLElementVisitor() { | ||
override fun visitBlock(block: HCLBlock) { | ||
val properties = block.`object`?.propertyList ?: return | ||
val model = ModelHelper.getBlockProperties(block).associateBy { it.name } | ||
for (p in properties) { | ||
val name = p.name | ||
val defaultValue = model[name]?.defaultValue ?: continue | ||
val value = p.value ?: continue | ||
if (isEquals(value, defaultValue)) { | ||
holder.registerProblem(p, "'$name' is set to its default value", ProblemHighlightType.LIKE_UNUSED_SYMBOL, DeletePropertyFix) | ||
} | ||
} | ||
} | ||
|
||
private fun isEquals(value: HCLValue, defaultValue: Any): Boolean { | ||
return when (value) { | ||
is HCLBooleanLiteral -> value.value == defaultValue | ||
is HCLNumberLiteral -> value.value == defaultValue | ||
is HCLStringLiteral -> value.value == defaultValue | ||
else -> false | ||
} | ||
} | ||
} | ||
|
||
private object DeletePropertyFix : LocalQuickFixBase("Delete property"), LowPriorityAction { | ||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { | ||
val property = descriptor.psiElement as? HCLProperty ?: return | ||
ApplicationManager.getApplication().runWriteAction { property.delete() } | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't see what
DeepEqual
was achieving here, perhaps something important that I misunderstood?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't remember why I've added it here. Does removing changes anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
!reflect.DeepEqual(...
there are 265 defaults generated. Removing it makes 3134 defaults generated. The diff is too big to compare everything but I think it is correctly finding all default values when removed.