ttsc 0.11.0 → 0.12.0

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.
Files changed (79) hide show
  1. package/README.md +25 -6
  2. package/cmd/ttsc/api_compile.go +101 -106
  3. package/cmd/ttsc/api_compile_branches_test.go +81 -0
  4. package/cmd/ttsc/api_transform.go +49 -54
  5. package/cmd/ttsc/api_transform_branches_test.go +41 -0
  6. package/cmd/ttsc/build.go +108 -109
  7. package/cmd/ttsc/build_branches_test.go +130 -0
  8. package/cmd/ttsc/helpers_test.go +47 -0
  9. package/cmd/ttsc/main.go +104 -103
  10. package/cmd/ttsc/main_alias_branches_test.go +29 -0
  11. package/cmd/ttscserver/main.go +104 -96
  12. package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +42 -0
  13. package/cmd/ttscserver/run_lsp_reports_server_error_test.go +23 -25
  14. package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +44 -0
  15. package/cmd/utility-host/main.go +42 -0
  16. package/driver/lsp.go +60 -0
  17. package/driver/plugins.go +122 -0
  18. package/driver/program.go +346 -356
  19. package/driver/rewrite.go +336 -339
  20. package/{driver → internal/lspserver}/lsp_envelope.go +70 -64
  21. package/{driver → internal/lspserver}/lsp_frame.go +62 -62
  22. package/{driver → internal/lspserver}/lsp_plugin.go +58 -58
  23. package/internal/lspserver/lsp_proxy.go +391 -0
  24. package/internal/lspserver/lsp_server.go +231 -0
  25. package/lib/TtscCompiler.d.ts +3 -3
  26. package/lib/TtscCompiler.js +20 -6
  27. package/lib/TtscCompiler.js.map +1 -1
  28. package/lib/compiler/internal/resolveTsgo.d.ts +1 -0
  29. package/lib/compiler/internal/resolveTsgo.js +15 -4
  30. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  31. package/lib/compiler/internal/runBuild.js +12 -4
  32. package/lib/compiler/internal/runBuild.js.map +1 -1
  33. package/lib/compiler/internal/sharedHostHelpers.d.ts +12 -33
  34. package/lib/compiler/internal/sharedHostHelpers.js +27 -70
  35. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  36. package/lib/compiler/internal/transformProjectInMemory.js +12 -5
  37. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  38. package/lib/index.d.ts +0 -1
  39. package/lib/index.js +0 -1
  40. package/lib/index.js.map +1 -1
  41. package/lib/launcher/internal/runTtsc.js +9 -4
  42. package/lib/launcher/internal/runTtsc.js.map +1 -1
  43. package/lib/launcher/internal/runTtscserver.d.ts +1 -0
  44. package/lib/launcher/internal/runTtscserver.js +38 -1
  45. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  46. package/lib/plugin/internal/buildSourcePlugin.d.ts +3 -0
  47. package/lib/plugin/internal/buildSourcePlugin.js +444 -41
  48. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  49. package/lib/plugin/internal/loadProjectPlugins.js +153 -34
  50. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  51. package/lib/structures/ITtscCompilerContext.d.ts +2 -1
  52. package/lib/structures/ITtscPlugin.d.ts +8 -13
  53. package/lib/structures/ITtscPluginFactoryContext.d.ts +4 -5
  54. package/lib/structures/ITtscProjectPluginConfig.d.ts +2 -2
  55. package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +4 -2
  56. package/package.json +8 -8
  57. package/shim/lsp/shim.go +11 -17
  58. package/src/TtscCompiler.ts +20 -7
  59. package/src/compiler/internal/resolveTsgo.ts +18 -5
  60. package/src/compiler/internal/runBuild.ts +17 -4
  61. package/src/compiler/internal/sharedHostHelpers.ts +34 -67
  62. package/src/compiler/internal/transformProjectInMemory.ts +18 -5
  63. package/src/index.ts +0 -1
  64. package/src/launcher/internal/runTtsc.ts +9 -4
  65. package/src/launcher/internal/runTtscserver.ts +43 -1
  66. package/src/plugin/internal/buildSourcePlugin.ts +475 -41
  67. package/src/plugin/internal/loadProjectPlugins.ts +199 -39
  68. package/src/structures/ITtscCompilerContext.ts +2 -1
  69. package/src/structures/ITtscPlugin.ts +9 -13
  70. package/src/structures/ITtscPluginFactoryContext.ts +4 -5
  71. package/src/structures/ITtscProjectPluginConfig.ts +2 -2
  72. package/src/structures/internal/ITtscLoadedNativePlugin.ts +4 -2
  73. package/utility/host.go +267 -1296
  74. package/driver/lsp_proxy.go +0 -394
  75. package/driver/lsp_server.go +0 -197
  76. package/lib/defineTtscPlugin.d.ts +0 -18
  77. package/lib/defineTtscPlugin.js +0 -7
  78. package/lib/defineTtscPlugin.js.map +0 -1
  79. 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
- "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"
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
- File *ast.SourceFile
33
- RootName string
34
- Namespaces []string
35
- Method string
36
- Replacement string
37
- ConsumeParens bool
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
- byPath map[string][]Rewrite
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
- if r.File == nil {
51
- return
52
- }
53
- path := filepath.ToSlash(r.File.FileName())
54
- rs.byPath[path] = append(rs.byPath[path], r)
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
- n := 0
60
- for _, rws := range rs.byPath {
61
- n += len(rws)
62
- }
63
- return n
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
- return p.emit(rs, nil, writeFile)
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
- if p == nil || p.TSProgram == nil {
81
- return nil, nil, errors.New("driver: nil program")
82
- }
83
- wf := writeFile
84
- if wf == nil {
85
- wf = func(fileName, text string, data *shimcompiler.WriteFileData) error {
86
- return DefaultWriteFile(fileName, text)
87
- }
88
- }
89
- result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
90
- WriteFile: wf,
91
- })
92
- if result == nil {
93
- return nil, nil, errors.New("driver: Emit returned nil")
94
- }
95
- return result, convertDiagnostics(result.Diagnostics), nil
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
- return p.emit(rs, target, writeFile)
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
- if p == nil || p.TSProgram == nil {
106
- return nil, nil, errors.New("driver: nil program")
107
- }
108
- if rs == nil {
109
- rs = NewRewriteSet()
110
- }
111
- cursors := map[string]int{}
112
- wf := func(fileName, text string, data *shimcompiler.WriteFileData) error {
113
- // A patched file is idempotent: once the sentinel exists, the emitted text
114
- // is passed through unchanged. This matters for watch/rebuild loops and
115
- // tests that re-run emit over the same output directory.
116
- if strings.Contains(text, RewriteSentinel) {
117
- if writeFile != nil {
118
- return writeFile(fileName, text, data)
119
- }
120
- return DefaultWriteFile(fileName, text)
121
- }
122
- // Rewrites are matched after tsgo has printed JavaScript. The source-file
123
- // association is recovered from the output path because WriteFile receives
124
- // only the final file name and text.
125
- patched, err := applyRewrites(fileName, text, rs, cursors)
126
- if err != nil {
127
- return err
128
- }
129
- if patched != text {
130
- patched = insertSentinel(patched)
131
- }
132
- if writeFile != nil {
133
- return writeFile(fileName, patched, data)
134
- }
135
- return DefaultWriteFile(fileName, patched)
136
- }
137
-
138
- result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
139
- TargetSourceFile: target,
140
- WriteFile: wf,
141
- })
142
- if result == nil {
143
- return nil, nil, errors.New("driver: Emit returned nil")
144
- }
145
- return result, convertDiagnostics(result.Diagnostics), nil
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
- 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)
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
- 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
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
- 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
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
- 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
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
- 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
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
- 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
- if parenPos >= len(text) || text[parenPos] != '(' {
248
- return text, searchFrom, false, errors.New("driver: expected '(' after method name")
249
- }
250
- closePos, ok := matchParen(text, parenPos)
251
- if !ok {
252
- return text, searchFrom, false, errors.New("driver: unbalanced parens while locating plugin call")
253
- }
254
- if r.ConsumeParens {
255
- replaced := text[:idx] + r.Replacement + text[closePos+1:]
256
- return replaced, idx + len(r.Replacement), true, nil
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
- return []string{
264
- root,
265
- root + "_1.default",
266
- root + "_2.default",
267
- root + ".default",
268
- root + "_1",
269
- root + "_2",
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
- if len(r.Namespaces) == 0 {
275
- return r.RootName
276
- }
277
- return r.RootName + "." + strings.Join(r.Namespaces, ".")
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
- if len(r.Namespaces) == 0 {
282
- return "." + r.Method + "("
283
- }
284
- return "." + strings.Join(r.Namespaces, ".") + "." + r.Method + "("
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
- start := searchFrom
289
- if start < 0 {
290
- start = 0
291
- }
292
- for {
293
- hit := strings.Index(text[start:], needle)
294
- if hit < 0 {
295
- return -1
296
- }
297
- pos := start + hit
298
- if pos == 0 {
299
- return pos
300
- }
301
- prev := rune(text[pos-1])
302
- if isIdentifierPart(prev) {
303
- start = pos + 1
304
- continue
305
- }
306
- return pos
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
- return r == '_' || r == '$' || unicode.IsLetter(r) || unicode.IsDigit(r)
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
- if pos >= len(text) || text[pos] != '(' {
316
- return 0, false
317
- }
318
- depth := 1
319
- lastSignificant := byte('(')
320
- for i := pos + 1; i < len(text); i++ {
321
- ch := text[i]
322
- switch ch {
323
- case ' ', '\t', '\n', '\r', '\f':
324
- continue
325
- case '(':
326
- depth++
327
- lastSignificant = ch
328
- case ')':
329
- depth--
330
- if depth == 0 {
331
- return i, true
332
- }
333
- lastSignificant = ch
334
- case '"', '\'':
335
- end, ok := skipQuoted(text, i, ch)
336
- if !ok {
337
- return 0, false
338
- }
339
- i = end
340
- lastSignificant = 'x'
341
- case '`':
342
- end, ok := skipTemplate(text, i)
343
- if !ok {
344
- return 0, false
345
- }
346
- i = end
347
- lastSignificant = 'x'
348
- case '/':
349
- if i+1 < len(text) {
350
- switch text[i+1] {
351
- case '/':
352
- i = skipLineComment(text, i+2)
353
- continue
354
- case '*':
355
- end, ok := skipBlockComment(text, i+2)
356
- if !ok {
357
- return 0, false
358
- }
359
- i = end
360
- continue
361
- }
362
- }
363
- if canStartRegexLiteral(lastSignificant) {
364
- end, ok := skipRegexLiteral(text, i)
365
- if !ok {
366
- return 0, false
367
- }
368
- i = end
369
- lastSignificant = 'x'
370
- continue
371
- }
372
- lastSignificant = ch
373
- default:
374
- lastSignificant = ch
375
- }
376
- }
377
- return 0, false
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
- for i := pos + 1; i < len(text); i++ {
382
- switch text[i] {
383
- case '\\':
384
- i++
385
- case quote:
386
- return i, true
387
- case '\n', '\r':
388
- return 0, false
389
- }
390
- }
391
- return 0, false
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
- for i := pos + 1; i < len(text); i++ {
396
- switch text[i] {
397
- case '\\':
398
- i++
399
- case '`':
400
- return i, true
401
- }
402
- }
403
- return 0, false
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
- for i := pos; i < len(text); i++ {
408
- if text[i] == '\n' || text[i] == '\r' {
409
- return i
410
- }
411
- }
412
- return len(text) - 1
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
- for i := pos; i+1 < len(text); i++ {
417
- if text[i] == '*' && text[i+1] == '/' {
418
- return i + 1, true
419
- }
420
- }
421
- return 0, false
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
- return strings.ContainsRune("([{=,:;!&|?+-*~^<>%", rune(previous))
422
+ return strings.ContainsRune("([{=,:;!&|?+-*~^<>%", rune(previous))
426
423
  }
427
424
 
428
425
  func skipRegexLiteral(text string, pos int) (int, bool) {
429
- inClass := false
430
- for i := pos + 1; i < len(text); i++ {
431
- switch text[i] {
432
- case '\\':
433
- i++
434
- case '[':
435
- inClass = true
436
- case ']':
437
- inClass = false
438
- case '/':
439
- if inClass {
440
- continue
441
- }
442
- for i+1 < len(text) && isRegexFlag(text[i+1]) {
443
- i++
444
- }
445
- return i, true
446
- case '\n', '\r':
447
- return 0, false
448
- }
449
- }
450
- return 0, false
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
- return ch == '_' ||
455
- ch == '$' ||
456
- unicode.IsLetter(rune(ch)) ||
457
- unicode.IsDigit(rune(ch))
451
+ return ch == '_' ||
452
+ ch == '$' ||
453
+ unicode.IsLetter(rune(ch)) ||
454
+ unicode.IsDigit(rune(ch))
458
455
  }