// Package diagnostics contains generated localizable diagnostic messages. package diagnostics import ( "fmt" "regexp" "strconv" "strings" "sync" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/locale" "golang.org/x/text/language" ) //go:generate go run generate.go -diagnostics ./diagnostics_generated.go -loc ./loc_generated.go -locdir ./loc //go:generate go tool golang.org/x/tools/cmd/stringer -type=Category -output=stringer_generated.go //go:generate npx dprint fmt diagnostics_generated.go loc_generated.go stringer_generated.go type Category int32 const ( CategoryWarning Category = iota CategoryError CategorySuggestion CategoryMessage ) func (category Category) Name() string { switch category { case CategoryWarning: return "warning" case CategoryError: return "error" case CategorySuggestion: return "suggestion" case CategoryMessage: return "message" } panic("Unhandled diagnostic category") } type Key string type Message struct { code int32 category Category key Key text string reportsUnnecessary bool elidedInCompatibilityPyramid bool reportsDeprecated bool } func (m *Message) Code() int32 { return m.code } func (m *Message) Category() Category { return m.category } func (m *Message) Key() Key { return m.key } func (m *Message) ReportsUnnecessary() bool { return m.reportsUnnecessary } func (m *Message) ElidedInCompatibilityPyramid() bool { return m.elidedInCompatibilityPyramid } func (m *Message) ReportsDeprecated() bool { return m.reportsDeprecated } // For debugging only. func (m *Message) String() string { return m.text } func (m *Message) Localize(locale locale.Locale, args ...any) string { return Localize(locale, m, "", StringifyArgs(args)...) } func Localize(locale locale.Locale, message *Message, key Key, args ...string) string { if message == nil { message = keyToMessage(key) } if message == nil { panic("Unknown diagnostic message: " + string(key)) } text := message.text if localized, ok := getLocalizedMessages(language.Tag(locale))[message.key]; ok { text = localized } return Format(text, args) } var localizedMessagesCache sync.Map // map[language.Tag]map[Key]string func getLocalizedMessages(loc language.Tag) map[Key]string { if loc == language.Und { return nil } // Check cache first if cached, ok := localizedMessagesCache.Load(loc); ok { if cached == nil { return nil } return cached.(map[Key]string) } var messages map[Key]string _, index, confidence := matcher.Match(loc) if confidence >= language.Low && index >= 0 && index < len(localeFuncs) { if fn := localeFuncs[index]; fn != nil { messages = fn() } } localizedMessagesCache.Store(loc, messages) return messages } var placeholderRegexp = regexp.MustCompile(`{(\d+)}`) func Format(text string, args []string) string { if len(args) == 0 { return text } // Replace invalid UTF-8 with Unicode replacement character args = core.SameMap(args, func(arg string) string { return strings.ToValidUTF8(arg, "\uFFFD") }) return placeholderRegexp.ReplaceAllStringFunc(text, func(match string) string { index, err := strconv.ParseInt(match[1:len(match)-1], 10, 0) if err != nil || int(index) >= len(args) { panic("Invalid formatting placeholder") } return args[int(index)] }) } func StringifyArgs(args []any) []string { if len(args) == 0 { return nil } result := make([]string, len(args)) for i, arg := range args { if s, ok := arg.(string); ok { result[i] = s } else { result[i] = fmt.Sprintf("%v", arg) } } return result } func NewAdHocMessage(message string) *Message { return &Message{ code: -1, category: CategoryError, key: "-1", text: message, } }