Repository: ariga/atlas
Pull request #2816 (2024-05-30 13:01:59)

schemahcl: support recording hcl positions


Patch:
diff --git a/schemahcl/schemahcl.go b/schemahcl/schemahcl.go
index 0170b2d3..7c7c2ab3 100644
--- a/schemahcl/schemahcl.go
+++ b/schemahcl/schemahcl.go
@@ -34,7 +34,8 @@ type (
 		typedblk         map[string]map[string]BlockFunc
 		// Optional context to pass to dynamic block handlers,
 		// such as data-sources, type-blocks, etc.
-		ctx context.Context
+		ctx     context.Context
+		withPos bool
 	}
 	// Option configures a Config.
 	Option func(*Config)
@@ -98,6 +99,13 @@ func WithScopedEnums[T interface{ ~string }](path string, enums ...T) Option {
 	}
 }
 
+// WithPos attaches parse positions to returned Resources and Attrs.
+func WithPos() Option {
+	return func(c *Config) {
+		c.withPos = true
+	}
+}
+
 // WithVariables registers a list of variables to be injected into the context.
 func WithVariables(vars map[string]cty.Value) Option {
 	return func(c *Config) {
@@ -583,6 +591,9 @@ func (s *State) toAttrs(ctx *hcl.EvalContext, vr SchemaValidator, hclAttrs hclsy
 			return nil, err
 		}
 		at := &Attr{K: hclAttr.Name}
+		if s.config.withPos {
+			at.SetPos(&hclAttr.SrcRange.Start)
+		}
 		switch t := value.Type(); {
 		case isRef(value):
 			if !value.Type().HasAttribute("__ref") {
@@ -694,6 +705,9 @@ func (s *State) toResource(ctx *hcl.EvalContext, vr SchemaValidator, block *hcls
 		return nil, err
 	}
 	spec = &Resource{Type: block.Type}
+	if s.config.withPos {
+		spec.SetPos(&block.TypeRange.Start)
+	}
 	switch len(block.Labels) {
 	case 0:
 	case 1:
diff --git a/schemahcl/spec.go b/schemahcl/spec.go
index a2b0b88b..49ef9644 100644
--- a/schemahcl/spec.go
+++ b/schemahcl/spec.go
@@ -11,6 +11,8 @@ import (
 	"strings"
 
 	"ariga.io/atlas/sql/schema"
+
+	"github.com/hashicorp/hcl/v2"
 	"github.com/zclconf/go-cty/cty"
 	"github.com/zclconf/go-cty/cty/gocty"
 )
@@ -23,12 +25,14 @@ type (
 		Type      string
 		Attrs     []*Attr
 		Children  []*Resource
+		pos       *hcl.Pos
 	}
 
 	// Attr is an attribute of a Resource.
 	Attr struct {
-		K string
-		V cty.Value
+		K   string
+		V   cty.Value
+		pos *hcl.Pos
 	}
 
 	// Ref implements Value and represents a reference to another Resource.
@@ -379,6 +383,28 @@ func (a *Attr) Bools() (vs []bool, err error) {
 	return vs, nil
 }
 
+// SetPos sets the position of this attribute.
+func (a *Attr) SetPos(p *hcl.Pos) {
+	a.pos = p
+}
+
+// Pos returns the attribute position on the
+// file, or nil if it is not set.
+func (a *Attr) Pos() *hcl.Pos {
+	return a.pos
+}
+
+// SetPos sets the position of this resource.
+func (r *Resource) SetPos(p *hcl.Pos) {
+	r.pos = p
+}
+
+// Pos returns the resource position on the
+// file, or nil if it is not set.
+func (r *Resource) Pos() *hcl.Pos {
+	return r.pos
+}
+
 // Resource returns the first child Resource by its type and reports whether it was found.
 func (r *Resource) Resource(t string) (*Resource, bool) {
 	if r == nil {


Test patch:
diff --git a/schemahcl/schemahcl_test.go b/schemahcl/schemahcl_test.go
index b1fe830a..7a33d82b 100644
--- a/schemahcl/schemahcl_test.go
+++ b/schemahcl/schemahcl_test.go
@@ -1072,3 +1072,30 @@ bar "b1" {
 		Ref:  &Ref{V: "$foo.f1"},
 	}, doc.Bar[0])
 }
+
+func Test_WithPos(t *testing.T) {
+	var (
+		doc struct {
+			DefaultExtension
+		}
+		b = []byte(`
+foo {}
+foo {
+  bar {
+    baz = 1
+  }
+}
+baz = 1
+`)
+	)
+	require.NoError(t, New(WithPos()).EvalBytes(b, &doc, nil))
+	at, ok := doc.Extra.Attr("baz")
+	require.True(t, ok)
+	require.Equal(t, 8, at.Pos().Line)
+	rs := doc.Extra.Resources("foo")
+	require.Len(t, rs, 2)
+	require.Equal(t, 2, rs[0].Pos().Line)
+	require.Equal(t, 3, rs[1].Pos().Line)
+	require.Equal(t, 4, rs[1].Children[0].Pos().Line)
+	require.Equal(t, 5, rs[1].Children[0].Attrs[0].Pos().Line)
+}