diff --git a/internal/configs/named_values.go b/internal/configs/named_values.go index 13bcf9e41e..5196834c56 100644 --- a/internal/configs/named_values.go +++ b/internal/configs/named_values.go @@ -265,6 +265,7 @@ func lintVariableDefaultValue(expr hcl.Expression, targetTy cty.Type) hcl.Diagno nounPhrase, attrName, suggestion, ), Subject: unused.NameRange.ToHCL().Ptr(), + Context: unused.ContextRange.ToHCL().Ptr(), }) } diff --git a/internal/lang/lint/object_extra_attrs.go b/internal/lang/lint/object_extra_attrs.go index b2f62a878b..c49c8d8135 100644 --- a/internal/lang/lint/object_extra_attrs.go +++ b/internal/lang/lint/object_extra_attrs.go @@ -41,6 +41,13 @@ type DiscardedObjectConstructorAttr struct { // definition that is being reported. NameRange tfdiags.SourceRange + // ContextRange is the source range of relevant context surrounding the + // problematic attribute defition, intended to be used for the "Context" + // field of a generated diagnostic so that its included source code + // snippet will also include some surrounding lines that should indicate + // what else was set in the relevant object constructor. + ContextRange tfdiags.SourceRange + // TargetType is the leaf object type that the affected object constructor // was building a value for. This is the type that the last element of // Path would traverse into, and which (by definition) does not have an @@ -131,9 +138,10 @@ func yieldDiscardedObjectConstructorAttrsObject(expr hcl.Expression, targetTy ct copy(retPath, path) retPath = append(retPath, cty.GetAttrStep{Name: keyStr}) result := DiscardedObjectConstructorAttr{ - Path: retPath, - NameRange: tfdiags.SourceRangeFromHCL(item.KeyExpr.Range()), - TargetType: targetTy, + Path: retPath, + NameRange: tfdiags.SourceRangeFromHCL(item.KeyExpr.Range()), + ContextRange: tfdiags.SourceRangeFromHCL(expr.SrcRange), // the entire containing object literal + TargetType: targetTy, } if !yield(result) { return false diff --git a/internal/lang/lint/object_extra_attrs_test.go b/internal/lang/lint/object_extra_attrs_test.go index 608c8c8445..ace202adfb 100644 --- a/internal/lang/lint/object_extra_attrs_test.go +++ b/internal/lang/lint/object_extra_attrs_test.go @@ -38,6 +38,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 2, Column: 5, Byte: 6}, End: tfdiags.SourcePos{Line: 2, Column: 8, Byte: 9}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 3, Column: 5, Byte: 22}, + }, TargetType: cty.EmptyObject, }, }, @@ -56,6 +60,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 2, Column: 5, Byte: 6}, End: tfdiags.SourcePos{Line: 2, Column: 8, Byte: 9}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 3, Column: 5, Byte: 22}, + }, TargetType: cty.Object(map[string]cty.Type{ "bar": cty.String, }), @@ -90,6 +98,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 2, Column: 6, Byte: 7}, End: tfdiags.SourcePos{Line: 2, Column: 9, Byte: 10}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 5, Byte: 6}, + End: tfdiags.SourcePos{Line: 2, Column: 18, Byte: 19}, + }, TargetType: cty.EmptyObject, }, { @@ -98,6 +110,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 6, Byte: 26}, End: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 29}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 3, Column: 5, Byte: 25}, + End: tfdiags.SourcePos{Line: 3, Column: 19, Byte: 39}, + }, TargetType: cty.EmptyObject, }, }, @@ -116,6 +132,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 6, Byte: 27}, End: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 30}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 19, Byte: 20}, + End: tfdiags.SourcePos{Line: 4, Column: 6, Byte: 44}, + }, TargetType: cty.EmptyObject, }, }, @@ -145,6 +165,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 2, Column: 6, Byte: 7}, End: tfdiags.SourcePos{Line: 2, Column: 9, Byte: 10}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 5, Byte: 6}, + End: tfdiags.SourcePos{Line: 2, Column: 18, Byte: 19}, + }, TargetType: cty.EmptyObject, }, { @@ -153,6 +177,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 6, Byte: 26}, End: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 29}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 3, Column: 5, Byte: 25}, + End: tfdiags.SourcePos{Line: 3, Column: 19, Byte: 39}, + }, TargetType: cty.EmptyObject, }, }, @@ -171,6 +199,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 6, Byte: 27}, End: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 30}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 19, Byte: 20}, + End: tfdiags.SourcePos{Line: 4, Column: 6, Byte: 44}, + }, TargetType: cty.EmptyObject, }, }, @@ -200,6 +232,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 2, Column: 10, Byte: 11}, End: tfdiags.SourcePos{Line: 2, Column: 13, Byte: 14}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 9, Byte: 10}, + End: tfdiags.SourcePos{Line: 2, Column: 22, Byte: 23}, + }, TargetType: cty.EmptyObject, }, { @@ -208,6 +244,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 10, Byte: 34}, End: tfdiags.SourcePos{Line: 3, Column: 13, Byte: 37}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 33}, + End: tfdiags.SourcePos{Line: 3, Column: 23, Byte: 47}, + }, TargetType: cty.EmptyObject, }, }, @@ -226,6 +266,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 6, Byte: 32}, End: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 35}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 24, Byte: 25}, + End: tfdiags.SourcePos{Line: 4, Column: 6, Byte: 49}, + }, TargetType: cty.EmptyObject, }, }, @@ -258,6 +302,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 2, Column: 6, Byte: 7}, End: tfdiags.SourcePos{Line: 2, Column: 9, Byte: 10}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 5, Byte: 6}, + End: tfdiags.SourcePos{Line: 2, Column: 18, Byte: 19}, + }, TargetType: cty.EmptyObject, }, { @@ -266,6 +314,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 6, Byte: 26}, End: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 29}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 3, Column: 5, Byte: 25}, + End: tfdiags.SourcePos{Line: 3, Column: 19, Byte: 39}, + }, TargetType: cty.Object(map[string]cty.Type{"not_baz": cty.String}), }, }, @@ -311,6 +363,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 3, Column: 6, Byte: 17}, End: tfdiags.SourcePos{Line: 3, Column: 9, Byte: 20}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 2, Column: 9, Byte: 10}, + End: tfdiags.SourcePos{Line: 4, Column: 6, Byte: 34}, + }, TargetType: cty.EmptyObject, }, { @@ -319,6 +375,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 6, Column: 6, Byte: 50}, End: tfdiags.SourcePos{Line: 6, Column: 9, Byte: 53}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 5, Column: 9, Byte: 43}, + End: tfdiags.SourcePos{Line: 7, Column: 6, Byte: 68}, + }, TargetType: cty.Object(map[string]cty.Type{"not_baz": cty.String}), }, { @@ -327,6 +387,10 @@ func TestDiscardedObjectConstructorAttrs(t *testing.T) { Start: tfdiags.SourcePos{Line: 8, Column: 5, Byte: 73}, End: tfdiags.SourcePos{Line: 8, Column: 6, Byte: 74}, }, + ContextRange: tfdiags.SourceRange{ + Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, + End: tfdiags.SourcePos{Line: 9, Column: 5, Byte: 84}, + }, TargetType: cty.Object(map[string]cty.Type{ "a": cty.EmptyObject, "b": cty.Object(map[string]cty.Type{"not_baz": cty.String}), diff --git a/internal/tofu/node_module_variable.go b/internal/tofu/node_module_variable.go index 3232f9b4ce..973146a43e 100644 --- a/internal/tofu/node_module_variable.go +++ b/internal/tofu/node_module_variable.go @@ -292,6 +292,7 @@ func (n *nodeModuleVariable) warningDiags() tfdiags.Diagnostics { n.Addr.Variable.Name, extraPathClause, attrName, suggestion, ), Subject: unused.NameRange.ToHCL().Ptr(), + Context: unused.ContextRange.ToHCL().Ptr(), }) } diff --git a/internal/tofu/node_module_variable_test.go b/internal/tofu/node_module_variable_test.go index 67dce73018..3d01d23252 100644 --- a/internal/tofu/node_module_variable_test.go +++ b/internal/tofu/node_module_variable_test.go @@ -322,12 +322,13 @@ func TestNodeModuleVariable_warningDiags(t *testing.T) { }), }, Expr: &hclsyntax.ObjectConsExpr{ + SrcRange: hcl.Range{Filename: "context1.tofu"}, Items: []hclsyntax.ObjectConsItem{ { KeyExpr: &hclsyntax.LiteralValueExpr{ Val: cty.StringVal("baz"), SrcRange: hcl.Range{ - Filename: "test.tofu", + Filename: "test1.tofu", }, }, ValueExpr: &hclsyntax.LiteralValueExpr{ @@ -342,12 +343,13 @@ func TestNodeModuleVariable_warningDiags(t *testing.T) { }, }, ValueExpr: &hclsyntax.ObjectConsExpr{ + SrcRange: hcl.Range{Filename: "context2.tofu"}, Items: []hclsyntax.ObjectConsItem{ { KeyExpr: &hclsyntax.LiteralValueExpr{ Val: cty.StringVal("beep"), SrcRange: hcl.Range{ - Filename: "test.tofu", + Filename: "test2.tofu", }, }, ValueExpr: &hclsyntax.LiteralValueExpr{ @@ -371,7 +373,10 @@ func TestNodeModuleVariable_warningDiags(t *testing.T) { Summary: "Object attribute is ignored", Detail: `The object type for input variable "foo" does not include an attribute named "baz", so this definition is unused. Did you mean to set attribute "bar" instead?`, Subject: &hcl.Range{ - Filename: "test.tofu", + Filename: "test1.tofu", // from synthetic source range in constructed expression above + }, + Context: &hcl.Range{ + Filename: "context1.tofu", // from synthetic source range in constructed expression above }, }) wantDiags = wantDiags.Append(&hcl.Diagnostic{ @@ -379,7 +384,10 @@ func TestNodeModuleVariable_warningDiags(t *testing.T) { Summary: "Object attribute is ignored", Detail: `The object type for input variable "foo" nested value .bar does not include an attribute named "beep", so this definition is unused.`, Subject: &hcl.Range{ - Filename: "test.tofu", + Filename: "test2.tofu", // from synthetic source range in constructed expression above + }, + Context: &hcl.Range{ + Filename: "context2.tofu", // from synthetic source range in constructed expression above }, }) wantDiags = wantDiags.ForRPC()