Skip to content

Commit f60d610

Browse files
fix: fixed incorrect line numbers in callstack when an error occurs
1 parent 9e6840f commit f60d610

File tree

4 files changed

+75
-20
lines changed

4 files changed

+75
-20
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/speakeasy-api/easytemplate
33
go 1.19
44

55
require (
6-
github.com/dop251/goja v0.0.0-20230216112155-746f7ebdc514
6+
github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7
77
github.com/dop251/goja_nodejs v0.0.0-20221211191749-434192f0843e
88
github.com/evanw/esbuild v0.17.8
99
github.com/go-sourcemap/sourcemap v2.1.3+incompatible
@@ -14,6 +14,7 @@ require (
1414
require (
1515
github.com/davecgh/go-spew v1.1.1 // indirect
1616
github.com/dlclark/regexp2 v1.7.0 // indirect
17+
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
1718
github.com/pmezard/go-difflib v1.0.0 // indirect
1819
golang.org/x/sys v0.3.0 // indirect
1920
golang.org/x/text v0.5.0 // indirect

go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
2+
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
3+
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
14
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
25
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
36
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -7,8 +10,8 @@ github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo
710
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
811
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
912
github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
10-
github.com/dop251/goja v0.0.0-20230216112155-746f7ebdc514 h1:4Gkw6jJWCNKRwhOvqZZXwmFezg0r6IHCi0g4+WzL/84=
11-
github.com/dop251/goja v0.0.0-20230216112155-746f7ebdc514/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
13+
github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7 h1:cVGkvrdHgyBkYeB6kMCaF5j2d9Bg4trgbIpcUrKrvk4=
14+
github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
1215
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
1316
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
1417
github.com/dop251/goja_nodejs v0.0.0-20221211191749-434192f0843e h1:DJ5cKH4HUYevCd09vMIbwc8U02eBKLFR2q1O1hSAJcY=
@@ -19,6 +22,9 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyL
1922
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
2023
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
2124
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
25+
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
26+
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
27+
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
2228
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
2329
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
2430
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@@ -60,6 +66,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
6066
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6167
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6268
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
69+
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6370
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6471
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6572
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -72,6 +79,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
7279
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
7380
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
7481
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
82+
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
7583
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
7684
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
7785
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

internal/template/template.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/dop251/goja"
1515
"github.com/speakeasy-api/easytemplate/internal/utils"
16+
"github.com/speakeasy-api/easytemplate/internal/vm"
1617
)
1718

1819
type (
@@ -43,7 +44,7 @@ type tmplContext struct {
4344
type VM interface {
4445
Get(name string) goja.Value
4546
Set(name string, value any) error
46-
Run(name string, src string) (goja.Value, error)
47+
Run(name string, src string, opts ...vm.Option) (goja.Value, error)
4748
ToObject(val goja.Value) *goja.Object
4849
}
4950

@@ -165,7 +166,7 @@ func (t *Templator) evaluateInlineScripts(vm VM, templatePath, content string) (
165166
return match[0], nil
166167
}
167168

168-
output, err := t.execSJSBlock(vm, match[2], templatePath)
169+
output, err := t.execSJSBlock(vm, match[2], templatePath, findJSBlockLineNumber(content, match[2]))
169170
if err != nil {
170171
return "", err
171172
}
@@ -181,19 +182,19 @@ func (t *Templator) evaluateInlineScripts(vm VM, templatePath, content string) (
181182
return evaluated, replacedLines, nil
182183
}
183184

184-
func (t *Templator) execSJSBlock(vm VM, js, templatePath string) (string, error) {
185-
currentRender := vm.Get("render")
185+
func (t *Templator) execSJSBlock(v VM, js, templatePath string, jsBlockLineNumber int) (string, error) {
186+
currentRender := v.Get("render")
186187

187188
c := newInlineScriptContext()
188-
if err := vm.Set("render", c.render); err != nil {
189+
if err := v.Set("render", c.render); err != nil {
189190
return "", fmt.Errorf("failed to set render function: %w", err)
190191
}
191192

192-
if _, err := vm.Run("inline", js); err != nil {
193-
return "", fmt.Errorf("failed to run inline script in %s:\n%s - %w", templatePath, js, err)
193+
if _, err := v.Run(templatePath, js, vm.WithScriptStartingLineNumber(templatePath, jsBlockLineNumber)); err != nil {
194+
return "", fmt.Errorf("failed to run inline script in %s:\n```sjs\n%ssjs```\n%w", templatePath, js, err)
194195
}
195196

196-
if err := vm.Set("render", currentRender); err != nil {
197+
if err := v.Set("render", currentRender); err != nil {
197198
return "", fmt.Errorf("failed to unset render function: %w", err)
198199
}
199200

@@ -247,3 +248,19 @@ func adjustLineNumber(name string, err error, replacedLines int) error {
247248

248249
return err
249250
}
251+
252+
func findJSBlockLineNumber(content string, block string) int {
253+
const replacement = "~-~BLOCK_REPLACEMENT~-~"
254+
255+
content = strings.Replace(content, block, replacement, 1)
256+
257+
lines := strings.Split(content, "\n")
258+
259+
for i, line := range lines {
260+
if strings.Contains(line, replacement) {
261+
return i + 1
262+
}
263+
}
264+
265+
return 0
266+
}

internal/vm/vm.go

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,28 @@ var (
2626
ErrRuntime = errors.New("script runtime failure")
2727
)
2828

29-
var lineNumberRegex = regexp.MustCompile(`(?s).*?:([0-9]+):([0-9]+)\([0-9]+\)`)
29+
var lineNumberRegex = regexp.MustCompile(`at ([^ ]*?):([0-9]+):([0-9]+)\([0-9]+\)`)
3030

3131
// VM is a wrapper around the goja runtime.
3232
type VM struct {
3333
*goja.Runtime
3434
}
3535

36+
type Options struct {
37+
scriptStartingLineNumbers map[string]int
38+
}
39+
40+
type Option func(*Options)
41+
42+
func WithScriptStartingLineNumber(scriptName string, startingLineNumber int) Option {
43+
return func(o *Options) {
44+
if o.scriptStartingLineNumbers == nil {
45+
o.scriptStartingLineNumbers = make(map[string]int)
46+
}
47+
o.scriptStartingLineNumbers[scriptName] = startingLineNumber
48+
}
49+
}
50+
3651
type program struct {
3752
prog *goja.Program
3853
sourceMap []byte
@@ -53,7 +68,12 @@ func New() (*VM, error) {
5368
}
5469

5570
// Run runs a script in the VM.
56-
func (v *VM) Run(name string, src string) (goja.Value, error) {
71+
func (v *VM) Run(name string, src string, opts ...Option) (goja.Value, error) {
72+
options := &Options{}
73+
for _, opt := range opts {
74+
opt(options)
75+
}
76+
5777
p, err := v.compile(name, src, true)
5878
if err != nil {
5979
return nil, err
@@ -74,27 +94,36 @@ func (v *VM) Run(name string, src string) (goja.Value, error) {
7494
}
7595

7696
fixedStackTrace, _ := utils.ReplaceAllStringSubmatchFunc(lineNumberRegex, jsErr.String(), func(match []string) (string, error) {
77-
const expectedMatches = 3
97+
const expectedMatches = 4
7898

7999
if len(match) != expectedMatches {
80100
return match[0], nil
81101
}
82102

83-
line, err := strconv.Atoi(match[1])
103+
file := match[1]
104+
105+
line, err := strconv.Atoi(match[2])
84106
if err != nil {
85107
return match[0], nil //nolint:nilerr
86108
}
87-
column, err := strconv.Atoi(match[2])
109+
column, err := strconv.Atoi(match[3])
88110
if err != nil {
89111
return match[0], nil //nolint:nilerr
90112
}
91113

92-
_, _, line, column, ok := m.Source(line, column)
93-
if !ok {
94-
return match[0], nil
114+
remappedSuffix := ""
115+
_, _, remappedLine, remappedColumn, ok := m.Source(line, column)
116+
if ok {
117+
line = remappedLine
118+
column = remappedColumn
119+
remappedSuffix = ":*"
120+
}
121+
122+
if startingLine, ok := options.scriptStartingLineNumbers[file]; ok {
123+
line += startingLine - 1
95124
}
96125

97-
return strings.Replace(match[0], fmt.Sprintf(":%s:%s", match[1], match[2]), fmt.Sprintf(":%d:%d", line, column), 1), nil
126+
return strings.Replace(match[0], fmt.Sprintf(":%s:%s", match[2], match[3]), fmt.Sprintf(":%d:%d%s", line, column, remappedSuffix), 1), nil
98127
})
99128

100129
return nil, fmt.Errorf("failed to run script %s: %w", fixedStackTrace, ErrRuntime)

0 commit comments

Comments
 (0)