Repository: segmentio/terraform-docs
Pull request #223 (2020-03-24 13:35:20)

Allow hiding the "Sensitive" column in markdown
### Prerequisites

Put an `x` into the box(es) that apply:

- [ ] This pull request fixes a bug.
- [x] This pull request adds a feature.
- [x] This pull request enhances existing functionality.
- [ ] This pull request introduces breaking change.

For more information, see the [Contributing Guide](https://github.com/segmentio/terraform-docs/tree/master/CONTRIBUTING.md).

### Description

Use case: for projects where all outputs are expected to not be sensitive, it adds noise. This PR adds the `--no-sensitive` option to the markdown generation command. This options removes the sensitive column. The column will show by default, this is an opt-out

### Checklist

Put an `x` into all boxes that apply:

- [x] I have read the [Contributing Guidelines](https://github.com/segmentio/terraform-docs/tree/master/CONTRIBUTING.md).

#### Tests

- [x] I have added tests to cover my changes.
- [x] All tests pass when I run `make test`.

#### Documentation

- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.

#### Code Style

- [x] My code follows the code style of this project.


codecov-io: # [Codecov](https://codecov.io/gh/segmentio/terraform-docs/pull/223?src=pr&el=h1) Report
> Merging [#223](https://codecov.io/gh/segmentio/terraform-docs/pull/223?src=pr&el=desc) into [master](https://codecov.io/gh/segmentio/terraform-docs/commit/7a4bc282d814094ad86b7619c31f974e35612929&el=desc) will **decrease** coverage by `0.40%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##           master     #223      +/-   ##
==========================================
- Coverage   92.15%   91.74%   -0.41%     
==========================================
  Files          20       13       -7     
  Lines         892      703     -189     
==========================================
- Hits          822      645     -177     
+ Misses         47       41       -6     
+ Partials       23       17       -6     
```


| [Impacted Files](https://codecov.io/gh/segmentio/terraform-docs/pull/223?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [internal/format/xml.go](https://codecov.io/gh/segmentio/terraform-docs/pull/223/diff?src=pr&el=tree#diff-aW50ZXJuYWwvZm9ybWF0L3htbC5nbw==) | | |
| [internal/format/json.go](https://codecov.io/gh/segmentio/terraform-docs/pull/223/diff?src=pr&el=tree#diff-aW50ZXJuYWwvZm9ybWF0L2pzb24uZ28=) | | |
| [internal/format/util.go](https://codecov.io/gh/segmentio/terraform-docs/pull/223/diff?src=pr&el=tree#diff-aW50ZXJuYWwvZm9ybWF0L3V0aWwuZ28=) | | |
| [internal/format/pretty.go](https://codecov.io/gh/segmentio/terraform-docs/pull/223/diff?src=pr&el=tree#diff-aW50ZXJuYWwvZm9ybWF0L3ByZXR0eS5nbw==) | | |
| [internal/format/yaml.go](https://codecov.io/gh/segmentio/terraform-docs/pull/223/diff?src=pr&el=tree#diff-aW50ZXJuYWwvZm9ybWF0L3lhbWwuZ28=) | | |


Patch:
diff --git a/cmd/markdown.go b/cmd/markdown.go
index 3f68f27..8a52106 100644
--- a/cmd/markdown.go
+++ b/cmd/markdown.go
@@ -37,6 +37,7 @@ var mdDocumentCmd = &cobra.Command{
 
 func init() {
 	markdownCmd.PersistentFlags().BoolVar(new(bool), "no-required", false, "do not show \"Required\" column or section")
+	markdownCmd.PersistentFlags().BoolVar(new(bool), "no-sensitive", false, "do not show \"Sensitive\" column or section")
 	markdownCmd.PersistentFlags().BoolVar(new(bool), "no-escape", false, "do not escape special characters")
 	markdownCmd.PersistentFlags().IntVar(&settings.MarkdownIndent, "indent", 2, "indention level of Markdown sections [1, 2, 3, 4, 5]")
 
diff --git a/cmd/root.go b/cmd/root.go
index a5952e8..0f0cb60 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -36,6 +36,7 @@ var rootCmd = &cobra.Command{
 		settings.SortByName = oppositeBool("no-sort")
 		settings.ShowRequired = oppositeBool("no-required")
 		settings.EscapeCharacters = oppositeBool("no-escape")
+		settings.ShowSensitivity = oppositeBool("no-sensitive")
 	},
 }
 
diff --git a/docs/formats/markdown-document.md b/docs/formats/markdown-document.md
index d9dc711..48082b2 100644
--- a/docs/formats/markdown-document.md
+++ b/docs/formats/markdown-document.md
@@ -27,6 +27,7 @@ terraform-docs markdown document [PATH] [flags]
       --no-outputs                     do not show outputs
       --no-providers                   do not show providers
       --no-required                    do not show "Required" column or section
+      --no-sensitive                   do not show "Sensitive" column or section
       --no-requirements                do not show module requirements
       --no-sort                        do no sort items
       --output-values                  inject output values into outputs
diff --git a/docs/formats/markdown-table.md b/docs/formats/markdown-table.md
index 5c2ee4c..7dbde30 100644
--- a/docs/formats/markdown-table.md
+++ b/docs/formats/markdown-table.md
@@ -27,6 +27,7 @@ terraform-docs markdown table [PATH] [flags]
       --no-outputs                     do not show outputs
       --no-providers                   do not show providers
       --no-required                    do not show "Required" column or section
+      --no-sensitive                   do not show "Sensitive" column or section
       --no-requirements                do not show module requirements
       --no-sort                        do no sort items
       --output-values                  inject output values into outputs
diff --git a/docs/formats/markdown.md b/docs/formats/markdown.md
index 855bd1c..7710064 100644
--- a/docs/formats/markdown.md
+++ b/docs/formats/markdown.md
@@ -17,6 +17,7 @@ terraform-docs markdown [PATH] [flags]
       --indent int    indention level of Markdown sections [1, 2, 3, 4, 5] (default 2)
       --no-escape     do not escape special characters
       --no-required   do not show "Required" column or section
+      --no-sensitive  do not show "Sensitive" column or section
 ```
 
 ### Options inherited from parent commands
diff --git a/go.mod b/go.mod
index 80253b5..d036abd 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.14
 
 require (
 	github.com/hashicorp/terraform-config-inspect v0.0.0-20200210163047-f7d8399e1194
-	github.com/imdario/mergo v0.3.8
+	github.com/imdario/mergo v0.3.9
 	github.com/spf13/cobra v0.0.6
 	github.com/stretchr/testify v1.5.1
 	gopkg.in/yaml.v2 v2.2.8
diff --git a/go.sum b/go.sum
index 3d1a3d2..38983c4 100644
--- a/go.sum
+++ b/go.sum
@@ -56,8 +56,8 @@ github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY
 github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
 github.com/hashicorp/terraform-config-inspect v0.0.0-20200210163047-f7d8399e1194 h1:Zn54YFKlZ0tVcR4HCfmVUP9kTTIK1c7G5mW7JKMsaak=
 github.com/hashicorp/terraform-config-inspect v0.0.0-20200210163047-f7d8399e1194/go.mod h1:p+ivJws3dpqbp1iP84+npOyAmTTOLMgCzrXd3GSdn/A=
-github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
-github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
diff --git a/internal/format/document.go b/internal/format/document.go
index 48c5839..1a25b38 100644
--- a/internal/format/document.go
+++ b/internal/format/document.go
@@ -113,7 +113,9 @@ const (
 					{{- $sensitive := ternary .Sensitive "<sensitive>" .GetValue -}}
 					Value: {{ value $sensitive | sanitizeDoc }}
 
-					Sensitive: {{ ternary (.Sensitive) "yes" "no" }}
+					{{ if $.Settings.ShowSensitivity -}}
+						Sensitive: {{ ternary (.Sensitive) "yes" "no" }}
+					{{- end }}
 				{{ end }}
 			{{ end }}
 		{{ end }}
diff --git a/internal/format/table.go b/internal/format/table.go
index 01f5cc1..0dd1cc9 100644
--- a/internal/format/table.go
+++ b/internal/format/table.go
@@ -72,13 +72,16 @@ const (
 		{{ if not .Module.Outputs }}
 			No output.
 		{{ else }}
-			| Name | Description |{{ if .Settings.OutputValues }} Value | Sensitive |{{ end }}
-			|------|-------------|{{ if .Settings.OutputValues }}-------|:---------:|{{ end }}
+			| Name | Description |{{ if .Settings.OutputValues }} Value |{{ if $.Settings.ShowSensitivity }} Sensitive |{{ end }}{{ end }}
+			|------|-------------|{{ if .Settings.OutputValues }}-------|{{ if $.Settings.ShowSensitivity }}:---------:|{{ end }}{{ end }}
 			{{- range .Module.Outputs }}
 				| {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} |
 				{{- if $.Settings.OutputValues -}}
 					{{- $sensitive := ternary .Sensitive "<sensitive>" .GetValue -}}
-					{{ printf " " }}{{ value $sensitive | sanitizeTbl }} | {{ ternary (.Sensitive) "yes" "no" }} |
+					{{ printf " " }}{{ value $sensitive | sanitizeTbl }} |
+					{{- if $.Settings.ShowSensitivity -}} 
+						{{ printf " " }}{{ ternary (.Sensitive) "yes" "no" }} |
+					{{- end -}}
 				{{- end -}}
 			{{- end }}
 		{{ end }}
diff --git a/pkg/print/settings.go b/pkg/print/settings.go
index 76279a7..a9a039c 100644
--- a/pkg/print/settings.go
+++ b/pkg/print/settings.go
@@ -42,6 +42,10 @@ type Settings struct {
 	// scope: Markdown
 	ShowRequired bool
 
+	// ShowSensitivity show "Sensitive" column when generating Markdown (default: true)
+	// scope: Markdown
+	ShowSensitivity bool
+
 	// ShowRequirements show "Requirements" section (default: true)
 	// scope: Global
 	ShowRequirements bool
@@ -68,6 +72,7 @@ func NewSettings() *Settings {
 		ShowOutputs:      true,
 		ShowProviders:    true,
 		ShowRequired:     true,
+		ShowSensitivity:  true,
 		ShowRequirements: true,
 		SortByName:       true,
 		SortByRequired:   false,
diff --git a/vendor/github.com/imdario/mergo/.deepsource.toml b/vendor/github.com/imdario/mergo/.deepsource.toml
new file mode 100644
index 0000000..8a0681a
--- /dev/null
+++ b/vendor/github.com/imdario/mergo/.deepsource.toml
@@ -0,0 +1,12 @@
+version = 1
+
+test_patterns = [
+  "*_test.go"
+]
+
+[[analyzers]]
+name = "go"
+enabled = true
+
+  [analyzers.meta]
+  import_path = "github.com/imdario/mergo"
\ No newline at end of file
diff --git a/vendor/github.com/imdario/mergo/map.go b/vendor/github.com/imdario/mergo/map.go
index 3f5afa8..d83258b 100644
--- a/vendor/github.com/imdario/mergo/map.go
+++ b/vendor/github.com/imdario/mergo/map.go
@@ -99,11 +99,11 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
 				continue
 			}
 			if srcKind == dstKind {
-				if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+				if _, err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
 					return
 				}
 			} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
-				if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
+				if _, err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
 					return
 				}
 			} else if srcKind == reflect.Map {
@@ -157,7 +157,8 @@ func _map(dst, src interface{}, opts ...func(*Config)) error {
 	// To be friction-less, we redirect equal-type arguments
 	// to deepMerge. Only because arguments can be anything.
 	if vSrc.Kind() == vDst.Kind() {
-		return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+		_, err := deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+		return err
 	}
 	switch vSrc.Kind() {
 	case reflect.Struct:
diff --git a/vendor/github.com/imdario/mergo/merge.go b/vendor/github.com/imdario/mergo/merge.go
index 3fb6c64..3332c9c 100644
--- a/vendor/github.com/imdario/mergo/merge.go
+++ b/vendor/github.com/imdario/mergo/merge.go
@@ -11,20 +11,32 @@ package mergo
 import (
 	"fmt"
 	"reflect"
+	"unsafe"
 )
 
 func hasExportedField(dst reflect.Value) (exported bool) {
 	for i, n := 0, dst.NumField(); i < n; i++ {
 		field := dst.Type().Field(i)
-		if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
-			exported = exported || hasExportedField(dst.Field(i))
-		} else {
-			exported = exported || len(field.PkgPath) == 0
+		if isExportedComponent(&field) {
+			return true
 		}
 	}
 	return
 }
 
+func isExportedComponent(field *reflect.StructField) bool {
+	name := field.Name
+	pkgPath := field.PkgPath
+	if len(pkgPath) > 0 {
+		return false
+	}
+	c := name[0]
+	if 'a' <= c && c <= 'z' || c == '_' {
+		return false
+	}
+	return true
+}
+
 type Config struct {
 	Overwrite                    bool
 	AppendSlice                  bool
@@ -41,16 +53,17 @@ type Transformers interface {
 // Traverses recursively both values, assigning src's fields values to dst.
 // The map argument tracks comparisons that have already been seen, which allows
 // short circuiting on recursive types.
-func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
+func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (dst reflect.Value, err error) {
+	dst = dstIn
 	overwrite := config.Overwrite
 	typeCheck := config.TypeCheck
 	overwriteWithEmptySrc := config.overwriteWithEmptyValue
 	overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
-	config.overwriteWithEmptyValue = false
 
 	if !src.IsValid() {
 		return
 	}
+
 	if dst.CanAddr() {
 		addr := dst.UnsafeAddr()
 		h := 17 * addr
@@ -58,7 +71,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
 		typ := dst.Type()
 		for p := seen; p != nil; p = p.next {
 			if p.ptr == addr && p.typ == typ {
-				return nil
+				return dst, nil
 			}
 		}
 		// Remember, remember...
@@ -72,114 +85,124 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
 		}
 	}
 
+	if dst.IsValid() && src.IsValid() && src.Type() != dst.Type() {
+		err = fmt.Errorf("cannot append two different types (%s, %s)", src.Kind(), dst.Kind())
+		return
+	}
+
 	switch dst.Kind() {
 	case reflect.Struct:
 		if hasExportedField(dst) {
+			dstCp := reflect.New(dst.Type()).Elem()
 			for i, n := 0, dst.NumField(); i < n; i++ {
-				if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
+				dstField := dst.Field(i)
+				structField := dst.Type().Field(i)
+				// copy un-exported struct fields
+				if !isExportedComponent(&structField) {
+					rf := dstCp.Field(i)
+					rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() //nolint:gosec
+					dstRF := dst.Field(i)
+					if !dst.Field(i).CanAddr() {
+						continue
+					}
+
+					dstRF = reflect.NewAt(dstRF.Type(), unsafe.Pointer(dstRF.UnsafeAddr())).Elem() //nolint:gosec
+					rf.Set(dstRF)
+					continue
+				}
+				dstField, err = deepMerge(dstField, src.Field(i), visited, depth+1, config)
+				if err != nil {
 					return
 				}
+				dstCp.Field(i).Set(dstField)
+			}
+
+			if dst.CanSet() {
+				dst.Set(dstCp)
+			} else {
+				dst = dstCp
 			}
+			return
 		} else {
-			if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
-				dst.Set(src)
+			if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) {
+				dst = src
 			}
 		}
+
 	case reflect.Map:
 		if dst.IsNil() && !src.IsNil() {
-			dst.Set(reflect.MakeMap(dst.Type()))
+			if dst.CanSet() {
+				dst.Set(reflect.MakeMap(dst.Type()))
+			} else {
+				dst = src
+				return
+			}
 		}
 		for _, key := range src.MapKeys() {
 			srcElement := src.MapIndex(key)
+			dstElement := dst.MapIndex(key)
 			if !srcElement.IsValid() {
 				continue
 			}
-			dstElement := dst.MapIndex(key)
-			switch srcElement.Kind() {
-			case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
-				if srcElement.IsNil() {
-					continue
-				}
-				fallthrough
-			default:
-				if !srcElement.CanInterface() {
-					continue
-				}
-				switch reflect.TypeOf(srcElement.Interface()).Kind() {
-				case reflect.Struct:
-					fallthrough
-				case reflect.Ptr:
-					fallthrough
-				case reflect.Map:
-					srcMapElm := srcElement
-					dstMapElm := dstElement
-					if srcMapElm.CanInterface() {
-						srcMapElm = reflect.ValueOf(srcMapElm.Interface())
-						if dstMapElm.IsValid() {
-							dstMapElm = reflect.ValueOf(dstMapElm.Interface())
-						}
-					}
-					if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
-						return
-					}
-				case reflect.Slice:
-					srcSlice := reflect.ValueOf(srcElement.Interface())
-
-					var dstSlice reflect.Value
-					if !dstElement.IsValid() || dstElement.IsNil() {
-						dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
-					} else {
-						dstSlice = reflect.ValueOf(dstElement.Interface())
-					}
-
-					if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
-						if typeCheck && srcSlice.Type() != dstSlice.Type() {
-							return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
-						}
-						dstSlice = srcSlice
-					} else if config.AppendSlice {
-						if srcSlice.Type() != dstSlice.Type() {
-							return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
-						}
-						dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
-					}
-					dst.SetMapIndex(key, dstSlice)
+			if dst.MapIndex(key).IsValid() {
+				k := dstElement.Interface()
+				dstElement = reflect.ValueOf(k)
+			}
+			if isReflectNil(srcElement) {
+				if overwrite || isReflectNil(dstElement) {
+					dst.SetMapIndex(key, srcElement)
 				}
+				continue
 			}
-			if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) {
+			if !srcElement.CanInterface() {
 				continue
 			}
 
-			if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) {
-				if dst.IsNil() {
-					dst.Set(reflect.MakeMap(dst.Type()))
+			if srcElement.CanInterface() {
+				srcElement = reflect.ValueOf(srcElement.Interface())
+				if dstElement.IsValid() {
+					dstElement = reflect.ValueOf(dstElement.Interface())
 				}
-				dst.SetMapIndex(key, srcElement)
 			}
+			dstElement, err = deepMerge(dstElement, srcElement, visited, depth+1, config)
+			if err != nil {
+				return
+			}
+			dst.SetMapIndex(key, dstElement)
+
 		}
 	case reflect.Slice:
-		if !dst.CanSet() {
-			break
-		}
+		newSlice := dst
 		if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
-			dst.Set(src)
+			if typeCheck && src.Type() != dst.Type() {
+				return dst, fmt.Errorf("cannot override two slices with different type (%s, %s)", src.Type(), dst.Type())
+			}
+			newSlice = src
 		} else if config.AppendSlice {
-			if src.Type() != dst.Type() {
-				return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
+			if typeCheck && src.Type() != dst.Type() {
+				err = fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
+				return
 			}
-			dst.Set(reflect.AppendSlice(dst, src))
+			newSlice = reflect.AppendSlice(dst, src)
 		}
-	case reflect.Ptr:
-		fallthrough
-	case reflect.Interface:
-		if src.IsNil() {
+		if dst.CanSet() {
+			dst.Set(newSlice)
+		} else {
+			dst = newSlice
+		}
+	case reflect.Ptr, reflect.Interface:
+		if isReflectNil(src) {
 			break
 		}
 
 		if dst.Kind() != reflect.Ptr && src.Type().AssignableTo(dst.Type()) {
 			if dst.IsNil() || overwrite {
-				if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
-					dst.Set(src)
+				if overwrite || isEmptyValue(dst) {
+					if dst.CanSet() {
+						dst.Set(src)
+					} else {
+						dst = src
+					}
 				}
 			}
 			break
@@ -191,28 +214,38 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
 					dst.Set(src)
 				}
 			} else if src.Kind() == reflect.Ptr {
-				if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
+				if dst, err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
 					return
 				}
+				dst = dst.Addr()
 			} else if dst.Elem().Type() == src.Type() {
-				if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
+				if dst, err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
 					return
 				}
 			} else {
-				return ErrDifferentArgumentsTypes
+				return dst, ErrDifferentArgumentsTypes
 			}
 			break
 		}
 		if dst.IsNil() || overwrite {
-			if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
-				dst.Set(src)
+			if (overwrite || isEmptyValue(dst)) && (overwriteWithEmptySrc || !isEmptyValue(src)) {
+				if dst.CanSet() {
+					dst.Set(src)
+				} else {
+					dst = src
+				}
 			}
-		} else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
+		} else if _, err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
 			return
 		}
 	default:
-		if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
-			dst.Set(src)
+		overwriteFull := (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst))
+		if overwriteFull {
+			if dst.CanSet() {
+				dst.Set(src)
+			} else {
+				dst = src
+			}
 		}
 	}
 
@@ -246,7 +279,12 @@ func WithOverride(config *Config) {
 	config.Overwrite = true
 }
 
-// WithOverride will make merge override empty dst slice with empty src slice.
+// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
+func WithOverwriteWithEmptyValue(config *Config) {
+	config.overwriteWithEmptyValue = true
+}
+
+// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
 func WithOverrideEmptySlice(config *Config) {
 	config.overwriteSliceWithEmptyValue = true
 }
@@ -276,8 +314,25 @@ func merge(dst, src interface{}, opts ...func(*Config)) error {
 	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
 		return err
 	}
+	if !vDst.CanSet() {
+		return fmt.Errorf("cannot set dst, needs reference")
+	}
 	if vDst.Type() != vSrc.Type() {
 		return ErrDifferentArgumentsTypes
 	}
-	return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+	_, err = deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
+	return err
+}
+
+// IsReflectNil is the reflect value provided nil
+func isReflectNil(v reflect.Value) bool {
+	k := v.Kind()
+	switch k {
+	case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
+		// Both interface and slice are nil if first word is 0.
+		// Both are always bigger than a word; assume flagIndir.
+		return v.IsNil()
+	default:
+		return false
+	}
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 8bcd4a6..5e66a29 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -30,7 +30,7 @@ github.com/hashicorp/hcl/v2/json
 # github.com/hashicorp/terraform-config-inspect v0.0.0-20200210163047-f7d8399e1194
 ## explicit
 github.com/hashicorp/terraform-config-inspect/tfconfig
-# github.com/imdario/mergo v0.3.8
+# github.com/imdario/mergo v0.3.9
 ## explicit
 github.com/imdario/mergo
 # github.com/inconshreveable/mousetrap v1.0.0


Test patch:
diff --git a/internal/format/document_test.go b/internal/format/document_test.go
index 334fff5..76dd832 100644
--- a/internal/format/document_test.go
+++ b/internal/format/document_test.go
@@ -424,7 +424,8 @@ func TestDocumentIndentationOfFour(t *testing.T) {
 func TestDocumentOutputValues(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().With(&print.Settings{
-		OutputValues: true,
+		OutputValues:    true,
+		ShowSensitivity: true,
 	}).Build()
 
 	expected, err := testutil.GetExpected("document", "document-OutputValues")
@@ -467,3 +468,29 @@ func TestDocumentHeaderFromFile(t *testing.T) {
 	assert.Nil(err)
 	assert.Equal(expected, actual)
 }
+
+func TestDocumentOutputValuesNoSensitivity(t *testing.T) {
+	assert := assert.New(t)
+	settings := testutil.Settings().WithSections().With(&print.Settings{
+		OutputValues:    true,
+		ShowSensitivity: false,
+	}).Build()
+
+	expected, err := testutil.GetExpected("document", "document-OutputValuesNoSensitivity")
+	assert.Nil(err)
+
+	options, err := module.NewOptions().With(&module.Options{
+		OutputValues:     true,
+		OutputValuesPath: "output_values.json",
+	})
+	assert.Nil(err)
+
+	module, err := testutil.GetModule(options)
+	assert.Nil(err)
+
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
+
+	assert.Nil(err)
+	assert.Equal(expected, actual)
+}
diff --git a/internal/format/table_test.go b/internal/format/table_test.go
index 8042ba0..79f6ea7 100644
--- a/internal/format/table_test.go
+++ b/internal/format/table_test.go
@@ -424,7 +424,8 @@ func TestTableIndentationOfFour(t *testing.T) {
 func TestTableOutputValues(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().With(&print.Settings{
-		OutputValues: true,
+		OutputValues:    true,
+		ShowSensitivity: true,
 	}).Build()
 
 	expected, err := testutil.GetExpected("table", "table-OutputValues")
@@ -467,3 +468,29 @@ func TestTableHeaderFromFile(t *testing.T) {
 	assert.Nil(err)
 	assert.Equal(expected, actual)
 }
+
+func TestTableOutputValuesNoSensitivity(t *testing.T) {
+	assert := assert.New(t)
+	settings := testutil.Settings().WithSections().With(&print.Settings{
+		OutputValues:    true,
+		ShowSensitivity: false,
+	}).Build()
+
+	expected, err := testutil.GetExpected("table", "table-OutputValuesNoSensitivity")
+	assert.Nil(err)
+
+	options, err := module.NewOptions().With(&module.Options{
+		OutputValues:     true,
+		OutputValuesPath: "output_values.json",
+	})
+	assert.Nil(err)
+
+	module, err := testutil.GetModule(options)
+	assert.Nil(err)
+
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
+
+	assert.Nil(err)
+	assert.Equal(expected, actual)
+}
diff --git a/internal/format/testdata/document/document-OutputValuesNoSensitivity.golden b/internal/format/testdata/document/document-OutputValuesNoSensitivity.golden
new file mode 100644
index 0000000..589784b
--- /dev/null
+++ b/internal/format/testdata/document/document-OutputValuesNoSensitivity.golden
@@ -0,0 +1,345 @@
+Usage:
+
+Example of 'foo_bar' module in `foo_bar.tf`.
+
+- list item 1
+- list item 2
+
+Even inline **formatting** in _here_ is possible.  
+and some [link](https://domain.com/)
+
+* list item 3
+* list item 4
+
+```hcl
+module "foo_bar" {
+  source = "github.com/foo/bar"
+
+  id   = "1234567890"
+  name = "baz"
+
+  zones = ["us-east-1", "us-west-1"]
+
+  tags = {
+    Name         = "baz"
+    Created-By   = "first.last@email.com"
+    Date-Created = "20180101"
+  }
+}
+```
+
+Here is some trailing text after code block,  
+followed by another line of text.
+
+| Name | Description     |
+|------|-----------------|
+| Foo  | Foo description |
+| Bar  | Bar description |
+
+## Requirements
+
+The following requirements are needed by this module:
+
+- terraform (>= 0.12)
+
+- aws (>= 2.15.0)
+
+## Providers
+
+The following providers are used by this module:
+
+- tls
+
+- aws (>= 2.15.0)
+
+- aws.ident (>= 2.15.0)
+
+- null
+
+## Inputs
+
+The following input variables are supported:
+
+### unquoted
+
+Description: n/a
+
+Type: `any`
+
+Default: n/a
+
+### bool-3
+
+Description: n/a
+
+Type: `bool`
+
+Default: `true`
+
+### bool-2
+
+Description: It's bool number two.
+
+Type: `bool`
+
+Default: `false`
+
+### bool-1
+
+Description: It's bool number one.
+
+Type: `bool`
+
+Default: `true`
+
+### string-3
+
+Description: n/a
+
+Type: `string`
+
+Default: `""`
+
+### string-2
+
+Description: It's string number two.
+
+Type: `string`
+
+Default: n/a
+
+### string-1
+
+Description: It's string number one.
+
+Type: `string`
+
+Default: `"bar"`
+
+### number-3
+
+Description: n/a
+
+Type: `number`
+
+Default: `"19"`
+
+### number-4
+
+Description: n/a
+
+Type: `number`
+
+Default: `15.75`
+
+### number-2
+
+Description: It's number number two.
+
+Type: `number`
+
+Default: n/a
+
+### number-1
+
+Description: It's number number one.
+
+Type: `number`
+
+Default: `42`
+
+### map-3
+
+Description: n/a
+
+Type: `map`
+
+Default: `{}`
+
+### map-2
+
+Description: It's map number two.
+
+Type: `map`
+
+Default: n/a
+
+### map-1
+
+Description: It's map number one.
+
+Type: `map`
+
+Default:
+
+```json
+{
+  "a": 1,
+  "b": 2,
+  "c": 3
+}
+```
+
+### list-3
+
+Description: n/a
+
+Type: `list`
+
+Default: `[]`
+
+### list-2
+
+Description: It's list number two.
+
+Type: `list`
+
+Default: n/a
+
+### list-1
+
+Description: It's list number one.
+
+Type: `list`
+
+Default:
+
+```json
+[
+  "a",
+  "b",
+  "c"
+]
+```
+
+### input_with_underscores
+
+Description: A variable with underscores.
+
+Type: `any`
+
+Default: n/a
+
+### input-with-pipe
+
+Description: It includes v1 \| v2 \| v3
+
+Type: `string`
+
+Default: `"v1"`
+
+### input-with-code-block
+
+Description: This is a complicated one. We need a newline.  
+And an example in a code block
+```
+default     = [
+  "machine rack01:neptune"
+]
+```
+
+Type: `list`
+
+Default:
+
+```json
+[
+  "name rack:location"
+]
+```
+
+### long_type
+
+Description: This description is itself markdown.
+
+It spans over multiple lines.
+
+Type:
+
+```hcl
+object({
+    name = string,
+    foo  = object({ foo = string, bar = string }),
+    bar  = object({ foo = string, bar = string }),
+    fizz = list(string),
+    buzz = list(string)
+  })
+```
+
+Default:
+
+```json
+{
+  "bar": {
+    "bar": "bar",
+    "foo": "bar"
+  },
+  "buzz": [
+    "fizz",
+    "buzz"
+  ],
+  "fizz": [],
+  "foo": {
+    "bar": "foo",
+    "foo": "foo"
+  },
+  "name": "hello"
+}
+```
+
+### no-escape-default-value
+
+Description: The description contains `something_with_underscore`. Defaults to 'VALUE_WITH_UNDERSCORE'.
+
+Type: `string`
+
+Default: `"VALUE_WITH_UNDERSCORE"`
+
+### with-url
+
+Description: The description contains url. https://www.domain.com/foo/bar_baz.html
+
+Type: `string`
+
+Default: `""`
+
+## Outputs
+
+The following outputs are exported:
+
+### unquoted
+
+Description: It's unquoted output.
+
+Value:
+
+```json
+{
+  "leon": "cat"
+}
+```
+
+### output-2
+
+Description: It's output number two.
+
+Value:
+
+```json
+[
+  "jack",
+  "lola"
+]
+```
+
+### output-1
+
+Description: It's output number one.
+
+Value: `1`
+
+### output-0.12
+
+Description: terraform 0.12 only
+
+Value: `<sensitive>`
diff --git a/internal/format/testdata/table/table-OutputValuesNoSensitivity.golden b/internal/format/testdata/table/table-OutputValuesNoSensitivity.golden
new file mode 100644
index 0000000..52b2f76
--- /dev/null
+++ b/internal/format/testdata/table/table-OutputValuesNoSensitivity.golden
@@ -0,0 +1,90 @@
+Usage:
+
+Example of 'foo_bar' module in `foo_bar.tf`.
+
+- list item 1
+- list item 2
+
+Even inline **formatting** in _here_ is possible.  
+and some [link](https://domain.com/)
+
+* list item 3
+* list item 4
+
+```hcl
+module "foo_bar" {
+  source = "github.com/foo/bar"
+
+  id   = "1234567890"
+  name = "baz"
+
+  zones = ["us-east-1", "us-west-1"]
+
+  tags = {
+    Name         = "baz"
+    Created-By   = "first.last@email.com"
+    Date-Created = "20180101"
+  }
+}
+```
+
+Here is some trailing text after code block,  
+followed by another line of text.
+
+| Name | Description     |
+|------|-----------------|
+| Foo  | Foo description |
+| Bar  | Bar description |
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| terraform | >= 0.12 |
+| aws | >= 2.15.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| tls | n/a |
+| aws | >= 2.15.0 |
+| aws.ident | >= 2.15.0 |
+| null | n/a |
+
+## Inputs
+
+| Name | Description | Type | Default |
+|------|-------------|------|---------|
+| unquoted | n/a | `any` | n/a |
+| bool-3 | n/a | `bool` | `true` |
+| bool-2 | It's bool number two. | `bool` | `false` |
+| bool-1 | It's bool number one. | `bool` | `true` |
+| string-3 | n/a | `string` | `""` |
+| string-2 | It's string number two. | `string` | n/a |
+| string-1 | It's string number one. | `string` | `"bar"` |
+| number-3 | n/a | `number` | `"19"` |
+| number-4 | n/a | `number` | `15.75` |
+| number-2 | It's number number two. | `number` | n/a |
+| number-1 | It's number number one. | `number` | `42` |
+| map-3 | n/a | `map` | `{}` |
+| map-2 | It's map number two. | `map` | n/a |
+| map-1 | It's map number one. | `map` | <pre>{<br>  "a": 1,<br>  "b": 2,<br>  "c": 3<br>}</pre> |
+| list-3 | n/a | `list` | `[]` |
+| list-2 | It's list number two. | `list` | n/a |
+| list-1 | It's list number one. | `list` | <pre>[<br>  "a",<br>  "b",<br>  "c"<br>]</pre> |
+| input_with_underscores | A variable with underscores. | `any` | n/a |
+| input-with-pipe | It includes v1 \| v2 \| v3 | `string` | `"v1"` |
+| input-with-code-block | This is a complicated one. We need a newline.<br>And an example in a code block<pre>default     = [<br>  "machine rack01:neptune"<br>]</pre> | `list` | <pre>[<br>  "name rack:location"<br>]</pre> |
+| long_type | This description is itself markdown.<br><br>It spans over multiple lines. | <pre>object({<br>    name = string,<br>    foo  = object({ foo = string, bar = string }),<br>    bar  = object({ foo = string, bar = string }),<br>    fizz = list(string),<br>    buzz = list(string)<br>  })</pre> | <pre>{<br>  "bar": {<br>    "bar": "bar",<br>    "foo": "bar"<br>  },<br>  "buzz": [<br>    "fizz",<br>    "buzz"<br>  ],<br>  "fizz": [],<br>  "foo": {<br>    "bar": "foo",<br>    "foo": "foo"<br>  },<br>  "name": "hello"<br>}</pre> |
+| no-escape-default-value | The description contains `something_with_underscore`. Defaults to 'VALUE_WITH_UNDERSCORE'. | `string` | `"VALUE_WITH_UNDERSCORE"` |
+| with-url | The description contains url. https://www.domain.com/foo/bar_baz.html | `string` | `""` |
+
+## Outputs
+
+| Name | Description | Value |
+|------|-------------|-------|
+| unquoted | It's unquoted output. | <pre>{<br>  "leon": "cat"<br>}</pre> |
+| output-2 | It's output number two. | <pre>[<br>  "jack",<br>  "lola"<br>]</pre> |
+| output-1 | It's output number one. | `1` |
+| output-0.12 | terraform 0.12 only | `<sensitive>` |