ttsc 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -6
- package/cmd/ttsc/api_compile.go +101 -106
- package/cmd/ttsc/api_compile_branches_test.go +81 -0
- package/cmd/ttsc/api_transform.go +49 -54
- package/cmd/ttsc/api_transform_branches_test.go +41 -0
- package/cmd/ttsc/build.go +108 -109
- package/cmd/ttsc/build_branches_test.go +130 -0
- package/cmd/ttsc/helpers_test.go +47 -0
- package/cmd/ttsc/main.go +104 -103
- package/cmd/ttsc/main_alias_branches_test.go +29 -0
- package/cmd/ttscserver/main.go +104 -96
- package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +42 -0
- package/cmd/ttscserver/run_lsp_reports_server_error_test.go +23 -25
- package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +44 -0
- package/cmd/utility-host/main.go +42 -0
- package/driver/lsp.go +60 -0
- package/driver/plugins.go +122 -0
- package/driver/program.go +346 -356
- package/driver/rewrite.go +336 -339
- package/{driver → internal/lspserver}/lsp_envelope.go +70 -64
- package/{driver → internal/lspserver}/lsp_frame.go +62 -62
- package/{driver → internal/lspserver}/lsp_plugin.go +58 -58
- package/internal/lspserver/lsp_proxy.go +391 -0
- package/internal/lspserver/lsp_server.go +231 -0
- package/lib/TtscCompiler.d.ts +3 -3
- package/lib/TtscCompiler.js +20 -6
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/resolveTsgo.d.ts +1 -0
- package/lib/compiler/internal/resolveTsgo.js +15 -4
- package/lib/compiler/internal/resolveTsgo.js.map +1 -1
- package/lib/compiler/internal/runBuild.js +12 -4
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +12 -33
- package/lib/compiler/internal/sharedHostHelpers.js +27 -70
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +12 -5
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +9 -4
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +1 -0
- package/lib/launcher/internal/runTtscserver.js +38 -1
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +3 -0
- package/lib/plugin/internal/buildSourcePlugin.js +444 -41
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +153 -34
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +2 -1
- package/lib/structures/ITtscPlugin.d.ts +8 -13
- package/lib/structures/ITtscPluginFactoryContext.d.ts +4 -5
- package/lib/structures/ITtscProjectPluginConfig.d.ts +2 -2
- package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +4 -2
- package/package.json +8 -8
- package/shim/lsp/shim.go +11 -17
- package/src/TtscCompiler.ts +20 -7
- package/src/compiler/internal/resolveTsgo.ts +18 -5
- package/src/compiler/internal/runBuild.ts +17 -4
- package/src/compiler/internal/sharedHostHelpers.ts +34 -67
- package/src/compiler/internal/transformProjectInMemory.ts +18 -5
- package/src/index.ts +0 -1
- package/src/launcher/internal/runTtsc.ts +9 -4
- package/src/launcher/internal/runTtscserver.ts +43 -1
- package/src/plugin/internal/buildSourcePlugin.ts +475 -41
- package/src/plugin/internal/loadProjectPlugins.ts +199 -39
- package/src/structures/ITtscCompilerContext.ts +2 -1
- package/src/structures/ITtscPlugin.ts +9 -13
- package/src/structures/ITtscPluginFactoryContext.ts +4 -5
- package/src/structures/ITtscProjectPluginConfig.ts +2 -2
- package/src/structures/internal/ITtscLoadedNativePlugin.ts +4 -2
- package/utility/host.go +267 -1296
- package/driver/lsp_proxy.go +0 -394
- package/driver/lsp_server.go +0 -197
- package/lib/defineTtscPlugin.d.ts +0 -18
- package/lib/defineTtscPlugin.js +0 -7
- package/lib/defineTtscPlugin.js.map +0 -1
- package/src/defineTtscPlugin.ts +0 -26
package/driver/rewrite.go
CHANGED
|
@@ -14,32 +14,32 @@
|
|
|
14
14
|
package driver
|
|
15
15
|
|
|
16
16
|
import (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
"context"
|
|
18
|
+
"errors"
|
|
19
|
+
"fmt"
|
|
20
|
+
"os"
|
|
21
|
+
"path/filepath"
|
|
22
|
+
"strings"
|
|
23
|
+
"unicode"
|
|
24
|
+
|
|
25
|
+
"github.com/microsoft/typescript-go/shim/ast"
|
|
26
|
+
shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
// Rewrite describes one emit-time patch. Produced by CollectCallSites after
|
|
30
30
|
// the engine has generated a replacement JS fragment for the call.
|
|
31
31
|
type Rewrite struct {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
File *ast.SourceFile
|
|
33
|
+
RootName string
|
|
34
|
+
Namespaces []string
|
|
35
|
+
Method string
|
|
36
|
+
Replacement string
|
|
37
|
+
ConsumeParens bool
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// RewriteSet groups rewrites by file, preserving source order.
|
|
41
41
|
type RewriteSet struct {
|
|
42
|
-
|
|
42
|
+
byPath map[string][]Rewrite
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// NewRewriteSet returns an empty set.
|
|
@@ -47,20 +47,20 @@ func NewRewriteSet() *RewriteSet { return &RewriteSet{byPath: map[string][]Rewri
|
|
|
47
47
|
|
|
48
48
|
// Add registers a rewrite under the absolute path of its source file.
|
|
49
49
|
func (rs *RewriteSet) Add(r Rewrite) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
if r.File == nil {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
path := filepath.ToSlash(r.File.FileName())
|
|
54
|
+
rs.byPath[path] = append(rs.byPath[path], r)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Len returns the total number of rewrites across every file.
|
|
58
58
|
func (rs *RewriteSet) Len() int {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
n := 0
|
|
60
|
+
for _, rws := range rs.byPath {
|
|
61
|
+
n += len(rws)
|
|
62
|
+
}
|
|
63
|
+
return n
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// RewriteSentinel is the marker inserted at the top of a patched file so
|
|
@@ -72,387 +72,384 @@ const RewriteSentinel = "/* @ttsc-rewritten */"
|
|
|
72
72
|
// `writeFile` is nil, the patched JS is written to disk via the standard
|
|
73
73
|
// tsgo WriteFile.
|
|
74
74
|
func (p *Program) EmitAll(rs *RewriteSet, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) {
|
|
75
|
-
|
|
75
|
+
return p.emit(rs, nil, writeFile)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// EmitAllRaw runs TypeScript-Go emit without ttsc output-text rewrites.
|
|
79
79
|
func (p *Program) EmitAllRaw(writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
if p == nil || p.TSProgram == nil {
|
|
81
|
+
return nil, nil, errors.New("driver: nil program")
|
|
82
|
+
}
|
|
83
|
+
if err := p.ApplyLinkedPlugins(); err != nil {
|
|
84
|
+
return nil, nil, err
|
|
85
|
+
}
|
|
86
|
+
wf := writeFile
|
|
87
|
+
if wf == nil {
|
|
88
|
+
wf = func(fileName, text string, data *shimcompiler.WriteFileData) error {
|
|
89
|
+
return DefaultWriteFile(fileName, text)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
|
|
93
|
+
WriteFile: wf,
|
|
94
|
+
})
|
|
95
|
+
return result, convertDiagnostics(result.Diagnostics), nil
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// EmitFile runs tsgo's emitter for one source file, applying the same rewrite
|
|
99
99
|
// pipeline as EmitAll.
|
|
100
100
|
func (p *Program) EmitFile(rs *RewriteSet, target *ast.SourceFile, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) {
|
|
101
|
-
|
|
101
|
+
return p.emit(rs, target, writeFile)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
func (p *Program) emit(rs *RewriteSet, target *ast.SourceFile, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
105
|
+
if p == nil || p.TSProgram == nil {
|
|
106
|
+
return nil, nil, errors.New("driver: nil program")
|
|
107
|
+
}
|
|
108
|
+
if err := p.ApplyLinkedPlugins(); err != nil {
|
|
109
|
+
return nil, nil, err
|
|
110
|
+
}
|
|
111
|
+
if rs == nil {
|
|
112
|
+
rs = NewRewriteSet()
|
|
113
|
+
}
|
|
114
|
+
cursors := map[string]int{}
|
|
115
|
+
wf := func(fileName, text string, data *shimcompiler.WriteFileData) error {
|
|
116
|
+
// A patched file is idempotent: once the sentinel exists, the emitted text
|
|
117
|
+
// is passed through unchanged. This matters for watch/rebuild loops and
|
|
118
|
+
// tests that re-run emit over the same output directory.
|
|
119
|
+
if strings.Contains(text, RewriteSentinel) {
|
|
120
|
+
if writeFile != nil {
|
|
121
|
+
return writeFile(fileName, text, data)
|
|
122
|
+
}
|
|
123
|
+
return DefaultWriteFile(fileName, text)
|
|
124
|
+
}
|
|
125
|
+
// Rewrites are matched after tsgo has printed JavaScript. The source-file
|
|
126
|
+
// association is recovered from the output path because WriteFile receives
|
|
127
|
+
// only the final file name and text.
|
|
128
|
+
patched, err := applyRewrites(fileName, text, rs, cursors)
|
|
129
|
+
if err != nil {
|
|
130
|
+
return err
|
|
131
|
+
}
|
|
132
|
+
if patched != text {
|
|
133
|
+
patched = insertSentinel(patched)
|
|
134
|
+
}
|
|
135
|
+
if writeFile != nil {
|
|
136
|
+
return writeFile(fileName, patched, data)
|
|
137
|
+
}
|
|
138
|
+
return DefaultWriteFile(fileName, patched)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
|
|
142
|
+
TargetSourceFile: target,
|
|
143
|
+
WriteFile: wf,
|
|
144
|
+
})
|
|
145
|
+
return result, convertDiagnostics(result.Diagnostics), nil
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// DefaultWriteFile is the default disk writer used when EmitAll's caller does not
|
|
149
149
|
// supply a custom WriteFile callback.
|
|
150
150
|
func DefaultWriteFile(fileName, text string) error {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
if dir := filepath.Dir(fileName); dir != "" {
|
|
152
|
+
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
153
|
+
return err
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return os.WriteFile(fileName, []byte(text), 0o644)
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
func insertSentinel(text string) string {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
160
|
+
for _, prefix := range []string{"\"use strict\";\n", "'use strict';\n"} {
|
|
161
|
+
if strings.HasPrefix(text, prefix) {
|
|
162
|
+
return prefix + RewriteSentinel + "\n" + text[len(prefix):]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return RewriteSentinel + "\n" + text
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
func applyRewrites(outputName, text string, rs *RewriteSet, cursors map[string]int) (string, error) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
169
|
+
srcPath, ok := findSourceForOutput(outputName, rs)
|
|
170
|
+
if !ok || len(rs.byPath[srcPath]) == 0 {
|
|
171
|
+
return text, nil
|
|
172
|
+
}
|
|
173
|
+
rewrites := rs.byPath[srcPath]
|
|
174
|
+
pos := cursors[srcPath]
|
|
175
|
+
out := text
|
|
176
|
+
searchFrom := 0
|
|
177
|
+
for pos < len(rewrites) {
|
|
178
|
+
r := rewrites[pos]
|
|
179
|
+
replaced, nextSearchFrom, ok, err := spliceCall(out, r, searchFrom)
|
|
180
|
+
if err != nil {
|
|
181
|
+
return "", err
|
|
182
|
+
}
|
|
183
|
+
if !ok {
|
|
184
|
+
preview := out
|
|
185
|
+
if len(preview) > 400 {
|
|
186
|
+
preview = preview[:400] + "…"
|
|
187
|
+
}
|
|
188
|
+
return "", fmt.Errorf("driver: could not locate %s.%s(…) call in %s (tried roots %v; preview: %q)", joinRootAndNamespaces(r), r.Method, outputName, candidateRoots(r.RootName), preview)
|
|
189
|
+
}
|
|
190
|
+
out = replaced
|
|
191
|
+
searchFrom = nextSearchFrom
|
|
192
|
+
pos++
|
|
193
|
+
}
|
|
194
|
+
cursors[srcPath] = pos
|
|
195
|
+
return out, nil
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
func findSourceForOutput(outputName string, rs *RewriteSet) (string, bool) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
199
|
+
outSlash := strings.TrimSuffix(filepath.ToSlash(outputName), filepath.Ext(outputName))
|
|
200
|
+
var best string
|
|
201
|
+
bestScore := 0
|
|
202
|
+
for path := range rs.byPath {
|
|
203
|
+
srcStem := strings.TrimSuffix(filepath.ToSlash(path), filepath.Ext(path))
|
|
204
|
+
score := commonSuffixSegments(srcStem, outSlash)
|
|
205
|
+
if score > bestScore {
|
|
206
|
+
best = path
|
|
207
|
+
bestScore = score
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return best, bestScore > 0
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
func commonSuffixSegments(a, b string) int {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
as := strings.Split(a, "/")
|
|
215
|
+
bs := strings.Split(b, "/")
|
|
216
|
+
n := len(as)
|
|
217
|
+
if len(bs) < n {
|
|
218
|
+
n = len(bs)
|
|
219
|
+
}
|
|
220
|
+
shared := 0
|
|
221
|
+
for i := 1; i <= n; i++ {
|
|
222
|
+
if as[len(as)-i] != bs[len(bs)-i] {
|
|
223
|
+
break
|
|
224
|
+
}
|
|
225
|
+
shared++
|
|
226
|
+
}
|
|
227
|
+
return shared
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
func spliceCall(text string, r Rewrite, searchFrom int) (string, int, bool, error) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
replaced := text[:idx] + r.Replacement + text[idx+needleLen:]
|
|
259
|
-
return replaced, idx + len(r.Replacement), true, nil
|
|
231
|
+
aliases := candidateRoots(r.RootName)
|
|
232
|
+
tail := needleTail(r)
|
|
233
|
+
idx := -1
|
|
234
|
+
needleLen := 0
|
|
235
|
+
for _, cand := range aliases {
|
|
236
|
+
candNeedle := cand + tail
|
|
237
|
+
if i := indexAtCallStart(text, candNeedle, searchFrom); i >= 0 {
|
|
238
|
+
idx = i
|
|
239
|
+
needleLen = len(candNeedle) - 1
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if idx < 0 {
|
|
244
|
+
return text, searchFrom, false, nil
|
|
245
|
+
}
|
|
246
|
+
parenPos := idx + needleLen
|
|
247
|
+
closePos, ok := matchParen(text, parenPos)
|
|
248
|
+
if !ok {
|
|
249
|
+
return text, searchFrom, false, errors.New("driver: unbalanced parens while locating plugin call")
|
|
250
|
+
}
|
|
251
|
+
if r.ConsumeParens {
|
|
252
|
+
replaced := text[:idx] + r.Replacement + text[closePos+1:]
|
|
253
|
+
return replaced, idx + len(r.Replacement), true, nil
|
|
254
|
+
}
|
|
255
|
+
replaced := text[:idx] + r.Replacement + text[idx+needleLen:]
|
|
256
|
+
return replaced, idx + len(r.Replacement), true, nil
|
|
260
257
|
}
|
|
261
258
|
|
|
262
259
|
func candidateRoots(root string) []string {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
260
|
+
return []string{
|
|
261
|
+
root,
|
|
262
|
+
root + "_1.default",
|
|
263
|
+
root + "_2.default",
|
|
264
|
+
root + ".default",
|
|
265
|
+
root + "_1",
|
|
266
|
+
root + "_2",
|
|
267
|
+
}
|
|
271
268
|
}
|
|
272
269
|
|
|
273
270
|
func joinRootAndNamespaces(r Rewrite) string {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
271
|
+
if len(r.Namespaces) == 0 {
|
|
272
|
+
return r.RootName
|
|
273
|
+
}
|
|
274
|
+
return r.RootName + "." + strings.Join(r.Namespaces, ".")
|
|
278
275
|
}
|
|
279
276
|
|
|
280
277
|
func needleTail(r Rewrite) string {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
278
|
+
if len(r.Namespaces) == 0 {
|
|
279
|
+
return "." + r.Method + "("
|
|
280
|
+
}
|
|
281
|
+
return "." + strings.Join(r.Namespaces, ".") + "." + r.Method + "("
|
|
285
282
|
}
|
|
286
283
|
|
|
287
284
|
func indexAtCallStart(text, needle string, searchFrom int) int {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
285
|
+
start := searchFrom
|
|
286
|
+
if start < 0 {
|
|
287
|
+
start = 0
|
|
288
|
+
}
|
|
289
|
+
for {
|
|
290
|
+
hit := strings.Index(text[start:], needle)
|
|
291
|
+
if hit < 0 {
|
|
292
|
+
return -1
|
|
293
|
+
}
|
|
294
|
+
pos := start + hit
|
|
295
|
+
if pos == 0 {
|
|
296
|
+
return pos
|
|
297
|
+
}
|
|
298
|
+
prev := rune(text[pos-1])
|
|
299
|
+
if isIdentifierPart(prev) {
|
|
300
|
+
start = pos + 1
|
|
301
|
+
continue
|
|
302
|
+
}
|
|
303
|
+
return pos
|
|
304
|
+
}
|
|
308
305
|
}
|
|
309
306
|
|
|
310
307
|
func isIdentifierPart(r rune) bool {
|
|
311
|
-
|
|
308
|
+
return r == '_' || r == '$' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
|
312
309
|
}
|
|
313
310
|
|
|
314
311
|
func matchParen(text string, pos int) (int, bool) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
312
|
+
if pos >= len(text) || text[pos] != '(' {
|
|
313
|
+
return 0, false
|
|
314
|
+
}
|
|
315
|
+
depth := 1
|
|
316
|
+
lastSignificant := byte('(')
|
|
317
|
+
for i := pos + 1; i < len(text); i++ {
|
|
318
|
+
ch := text[i]
|
|
319
|
+
switch ch {
|
|
320
|
+
case ' ', '\t', '\n', '\r', '\f':
|
|
321
|
+
continue
|
|
322
|
+
case '(':
|
|
323
|
+
depth++
|
|
324
|
+
lastSignificant = ch
|
|
325
|
+
case ')':
|
|
326
|
+
depth--
|
|
327
|
+
if depth == 0 {
|
|
328
|
+
return i, true
|
|
329
|
+
}
|
|
330
|
+
lastSignificant = ch
|
|
331
|
+
case '"', '\'':
|
|
332
|
+
end, ok := skipQuoted(text, i, ch)
|
|
333
|
+
if !ok {
|
|
334
|
+
return 0, false
|
|
335
|
+
}
|
|
336
|
+
i = end
|
|
337
|
+
lastSignificant = 'x'
|
|
338
|
+
case '`':
|
|
339
|
+
end, ok := skipTemplate(text, i)
|
|
340
|
+
if !ok {
|
|
341
|
+
return 0, false
|
|
342
|
+
}
|
|
343
|
+
i = end
|
|
344
|
+
lastSignificant = 'x'
|
|
345
|
+
case '/':
|
|
346
|
+
if i+1 < len(text) {
|
|
347
|
+
switch text[i+1] {
|
|
348
|
+
case '/':
|
|
349
|
+
i = skipLineComment(text, i+2)
|
|
350
|
+
continue
|
|
351
|
+
case '*':
|
|
352
|
+
end, ok := skipBlockComment(text, i+2)
|
|
353
|
+
if !ok {
|
|
354
|
+
return 0, false
|
|
355
|
+
}
|
|
356
|
+
i = end
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if canStartRegexLiteral(lastSignificant) {
|
|
361
|
+
end, ok := skipRegexLiteral(text, i)
|
|
362
|
+
if !ok {
|
|
363
|
+
return 0, false
|
|
364
|
+
}
|
|
365
|
+
i = end
|
|
366
|
+
lastSignificant = 'x'
|
|
367
|
+
continue
|
|
368
|
+
}
|
|
369
|
+
lastSignificant = ch
|
|
370
|
+
default:
|
|
371
|
+
lastSignificant = ch
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return 0, false
|
|
378
375
|
}
|
|
379
376
|
|
|
380
377
|
func skipQuoted(text string, pos int, quote byte) (int, bool) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
378
|
+
for i := pos + 1; i < len(text); i++ {
|
|
379
|
+
switch text[i] {
|
|
380
|
+
case '\\':
|
|
381
|
+
i++
|
|
382
|
+
case quote:
|
|
383
|
+
return i, true
|
|
384
|
+
case '\n', '\r':
|
|
385
|
+
return 0, false
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return 0, false
|
|
392
389
|
}
|
|
393
390
|
|
|
394
391
|
func skipTemplate(text string, pos int) (int, bool) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
392
|
+
for i := pos + 1; i < len(text); i++ {
|
|
393
|
+
switch text[i] {
|
|
394
|
+
case '\\':
|
|
395
|
+
i++
|
|
396
|
+
case '`':
|
|
397
|
+
return i, true
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return 0, false
|
|
404
401
|
}
|
|
405
402
|
|
|
406
403
|
func skipLineComment(text string, pos int) int {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
404
|
+
for i := pos; i < len(text); i++ {
|
|
405
|
+
if text[i] == '\n' || text[i] == '\r' {
|
|
406
|
+
return i
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return len(text) - 1
|
|
413
410
|
}
|
|
414
411
|
|
|
415
412
|
func skipBlockComment(text string, pos int) (int, bool) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
413
|
+
for i := pos; i+1 < len(text); i++ {
|
|
414
|
+
if text[i] == '*' && text[i+1] == '/' {
|
|
415
|
+
return i + 1, true
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return 0, false
|
|
422
419
|
}
|
|
423
420
|
|
|
424
421
|
func canStartRegexLiteral(previous byte) bool {
|
|
425
|
-
|
|
422
|
+
return strings.ContainsRune("([{=,:;!&|?+-*~^<>%", rune(previous))
|
|
426
423
|
}
|
|
427
424
|
|
|
428
425
|
func skipRegexLiteral(text string, pos int) (int, bool) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
426
|
+
inClass := false
|
|
427
|
+
for i := pos + 1; i < len(text); i++ {
|
|
428
|
+
switch text[i] {
|
|
429
|
+
case '\\':
|
|
430
|
+
i++
|
|
431
|
+
case '[':
|
|
432
|
+
inClass = true
|
|
433
|
+
case ']':
|
|
434
|
+
inClass = false
|
|
435
|
+
case '/':
|
|
436
|
+
if inClass {
|
|
437
|
+
continue
|
|
438
|
+
}
|
|
439
|
+
for i+1 < len(text) && isRegexFlag(text[i+1]) {
|
|
440
|
+
i++
|
|
441
|
+
}
|
|
442
|
+
return i, true
|
|
443
|
+
case '\n', '\r':
|
|
444
|
+
return 0, false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return 0, false
|
|
451
448
|
}
|
|
452
449
|
|
|
453
450
|
func isRegexFlag(ch byte) bool {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
451
|
+
return ch == '_' ||
|
|
452
|
+
ch == '$' ||
|
|
453
|
+
unicode.IsLetter(rune(ch)) ||
|
|
454
|
+
unicode.IsDigit(rune(ch))
|
|
458
455
|
}
|