code.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package source
  2. import (
  3. "bufio"
  4. "fmt"
  5. "sort"
  6. "strings"
  7. "unicode"
  8. )
  9. type Edit struct {
  10. Location int
  11. Old string
  12. New string
  13. OldFunc func(string) int
  14. }
  15. type CommentSyntax struct {
  16. Dash bool
  17. Hash bool
  18. SlashStar bool
  19. }
  20. func LineNumber(source string, head int) (int, int) {
  21. // Calculate the true line and column number for a query, ignoring spaces
  22. var comment bool
  23. var loc, line, col int
  24. for i, char := range source {
  25. loc += 1
  26. col += 1
  27. // TODO: Check bounds
  28. if char == '-' && source[i+1] == '-' {
  29. comment = true
  30. }
  31. if char == '\n' {
  32. comment = false
  33. line += 1
  34. col = 0
  35. }
  36. if loc <= head {
  37. continue
  38. }
  39. if unicode.IsSpace(char) {
  40. continue
  41. }
  42. if comment {
  43. continue
  44. }
  45. break
  46. }
  47. return line + 1, col
  48. }
  49. func Pluck(source string, location, length int) (string, error) {
  50. head := location
  51. tail := location + length
  52. return source[head:tail], nil
  53. }
  54. func Mutate(raw string, a []Edit) (string, error) {
  55. if len(a) == 0 {
  56. return raw, nil
  57. }
  58. sort.Slice(a, func(i, j int) bool { return a[i].Location > a[j].Location })
  59. s := raw
  60. for idx, edit := range a {
  61. start := edit.Location
  62. if start > len(s) || start < 0 {
  63. return "", fmt.Errorf("edit start location is out of bounds")
  64. }
  65. var oldLen int
  66. if edit.OldFunc != nil {
  67. oldLen = edit.OldFunc(s[start:])
  68. } else {
  69. oldLen = len(edit.Old)
  70. }
  71. stop := edit.Location + oldLen
  72. if stop > len(s) {
  73. return "", fmt.Errorf("edit stop location is out of bounds")
  74. }
  75. // If this is not the first edit, (applied backwards), check if
  76. // this edit overlaps the previous one (and is therefore a developer error)
  77. if idx != 0 {
  78. prevEdit := a[idx-1]
  79. if prevEdit.Location < edit.Location+oldLen {
  80. return "", fmt.Errorf("2 edits overlap")
  81. }
  82. }
  83. s = s[:start] + edit.New + s[stop:]
  84. }
  85. return s, nil
  86. }
  87. func StripComments(sql string) (string, []string, error) {
  88. s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(sql)))
  89. var lines, comments []string
  90. for s.Scan() {
  91. t := s.Text()
  92. if strings.HasPrefix(t, "-- name:") {
  93. continue
  94. }
  95. if strings.HasPrefix(t, "/* name:") && strings.HasSuffix(t, "*/") {
  96. continue
  97. }
  98. if strings.HasPrefix(t, "# name:") {
  99. continue
  100. }
  101. if strings.HasPrefix(t, "--") {
  102. comments = append(comments, strings.TrimPrefix(t, "--"))
  103. continue
  104. }
  105. if strings.HasPrefix(t, "/*") && strings.HasSuffix(t, "*/") {
  106. t = strings.TrimPrefix(t, "/*")
  107. t = strings.TrimSuffix(t, "*/")
  108. comments = append(comments, t)
  109. continue
  110. }
  111. if strings.HasPrefix(t, "#") {
  112. comments = append(comments, strings.TrimPrefix(t, "#"))
  113. continue
  114. }
  115. lines = append(lines, t)
  116. }
  117. return strings.Join(lines, "\n"), comments, s.Err()
  118. }
  119. func CleanedComments(rawSQL string, cs CommentSyntax) ([]string, error) {
  120. s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(rawSQL)))
  121. var comments []string
  122. for s.Scan() {
  123. line := s.Text()
  124. var prefix string
  125. if strings.HasPrefix(line, "--") {
  126. if !cs.Dash {
  127. continue
  128. }
  129. prefix = "--"
  130. }
  131. if strings.HasPrefix(line, "/*") {
  132. if !cs.SlashStar {
  133. continue
  134. }
  135. prefix = "/*"
  136. }
  137. if strings.HasPrefix(line, "#") {
  138. if !cs.Hash {
  139. continue
  140. }
  141. prefix = "#"
  142. }
  143. if prefix == "" {
  144. continue
  145. }
  146. rest := line[len(prefix):]
  147. rest = strings.TrimSuffix(rest, "*/")
  148. comments = append(comments, rest)
  149. }
  150. return comments, s.Err()
  151. }