Files
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

173 lines
4.3 KiB
Go

package metadata
import (
"bufio"
"fmt"
"github.com/sqlc-dev/sqlc/internal/constants"
"strings"
"unicode"
"github.com/sqlc-dev/sqlc/internal/source"
)
type CommentSyntax source.CommentSyntax
type Metadata struct {
Name string
Cmd string
Comments []string
Params map[string]string
Flags map[string]bool
// RuleSkiplist contains the names of rules to disable vetting for.
// If the map is empty, but the disable vet flag is specified, then all rules are ignored.
RuleSkiplist map[string]struct{}
Filename string
}
const (
CmdExec = ":exec"
CmdExecResult = ":execresult"
CmdExecRows = ":execrows"
CmdExecLastId = ":execlastid"
CmdMany = ":many"
CmdOne = ":one"
CmdCopyFrom = ":copyfrom"
CmdBatchExec = ":batchexec"
CmdBatchMany = ":batchmany"
CmdBatchOne = ":batchone"
)
// A query name must be a valid Go identifier
//
// https://golang.org/ref/spec#Identifiers
func validateQueryName(name string) error {
if len(name) == 0 {
return fmt.Errorf("invalid query name: %q", name)
}
for i, c := range name {
isLetter := unicode.IsLetter(c) || c == '_'
isDigit := unicode.IsDigit(c)
if i == 0 && !isLetter {
return fmt.Errorf("invalid query name %q", name)
} else if !(isLetter || isDigit) {
return fmt.Errorf("invalid query name %q", name)
}
}
return nil
}
func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string, error) {
for line := range strings.SplitSeq(t, "\n") {
var prefix string
if strings.HasPrefix(line, "--") {
if !commentStyle.Dash {
continue
}
prefix = "--"
}
if strings.HasPrefix(line, "/*") {
if !commentStyle.SlashStar {
continue
}
prefix = "/*"
}
if strings.HasPrefix(line, "#") {
if !commentStyle.Hash {
continue
}
prefix = "#"
}
if prefix == "" {
continue
}
rest := line[len(prefix):]
if !strings.HasPrefix(strings.TrimSpace(rest), "name") {
continue
}
if !strings.Contains(rest, ":") {
continue
}
if !strings.HasPrefix(rest, " name: ") {
return "", "", fmt.Errorf("invalid metadata: %s", line)
}
part := strings.Split(strings.TrimSpace(line), " ")
if prefix == "/*" {
part = part[:len(part)-1] // removes the trailing "*/" element
}
if len(part) == 3 {
return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: %s", line)
}
if len(part) != 4 {
return "", "", fmt.Errorf("invalid query comment: %s", line)
}
queryName := part[2]
queryType := strings.TrimSpace(part[3])
switch queryType {
case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne:
default:
return "", "", fmt.Errorf("invalid query type: %s", queryType)
}
if err := validateQueryName(queryName); err != nil {
return "", "", err
}
return queryName, queryType, nil
}
return "", "", nil
}
// ParseCommentFlags processes the comments provided with queries to determine the metadata params, flags and rules to skip.
// All flags in query comments are prefixed with `@`, e.g. @param, @@sqlc-vet-disable.
func ParseCommentFlags(comments []string) (map[string]string, map[string]bool, map[string]struct{}, error) {
params := make(map[string]string)
flags := make(map[string]bool)
ruleSkiplist := make(map[string]struct{})
for _, line := range comments {
s := bufio.NewScanner(strings.NewReader(line))
s.Split(bufio.ScanWords)
s.Scan()
token := s.Text()
if !strings.HasPrefix(token, "@") {
continue
}
switch token {
case constants.QueryFlagParam:
s.Scan()
name := s.Text()
var rest []string
for s.Scan() {
paramToken := s.Text()
rest = append(rest, paramToken)
}
params[name] = strings.Join(rest, " ")
case constants.QueryFlagSqlcVetDisable:
flags[token] = true
// Vet rules can all be disabled in the same line or split across lines .i.e.
// /* @sqlc-vet-disable sqlc/db-prepare delete-without-where */
// is equivalent to:
// /* @sqlc-vet-disable sqlc/db-prepare */
// /* @sqlc-vet-disable delete-without-where */
for s.Scan() {
ruleSkiplist[s.Text()] = struct{}{}
}
default:
flags[token] = true
}
if s.Err() != nil {
return params, flags, ruleSkiplist, s.Err()
}
}
return params, flags, ruleSkiplist, nil
}