| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build go1.19 |
| // +build go1.19 |
| |
| package main |
| |
| import ( |
| "fmt" |
| "log" |
| "strings" |
| ) |
| |
| var typeNames = make(map[*Type]string) |
| var genTypes []*newType |
| |
| func findTypeNames(model Model) { |
| for _, s := range model.Structures { |
| for _, e := range s.Extends { |
| nameType(e, nil) // all references |
| } |
| for _, m := range s.Mixins { |
| nameType(m, nil) // all references |
| } |
| for _, p := range s.Properties { |
| nameType(p.Type, []string{s.Name, p.Name}) |
| } |
| } |
| for _, t := range model.Enumerations { |
| nameType(t.Type, []string{t.Name}) |
| } |
| for _, t := range model.TypeAliases { |
| nameType(t.Type, []string{t.Name}) |
| } |
| for _, r := range model.Requests { |
| nameType(r.Params, []string{"Param", r.Method}) |
| nameType(r.Result, []string{"Result", r.Method}) |
| nameType(r.RegistrationOptions, []string{"RegOpt", r.Method}) |
| } |
| for _, n := range model.Notifications { |
| nameType(n.Params, []string{"Param", n.Method}) |
| nameType(n.RegistrationOptions, []string{"RegOpt", n.Method}) |
| } |
| } |
| |
| // nameType populates typeNames[t] with the computed name of the type. |
| // path is the list of enclosing constructs in the JSON model. |
| func nameType(t *Type, path []string) string { |
| if t == nil || typeNames[t] != "" { |
| return "" |
| } |
| switch t.Kind { |
| case "base": |
| typeNames[t] = t.Name |
| return t.Name |
| case "reference": |
| typeNames[t] = t.Name |
| return t.Name |
| case "array": |
| nm := "[]" + nameType(t.Element, append(path, "Elem")) |
| typeNames[t] = nm |
| return nm |
| case "map": |
| key := nameType(t.Key, nil) // never a generated type |
| value := nameType(t.Value.(*Type), append(path, "Value")) |
| nm := "map[" + key + "]" + value |
| typeNames[t] = nm |
| return nm |
| // generated types |
| case "and": |
| nm := nameFromPath("And", path) |
| typeNames[t] = nm |
| for _, it := range t.Items { |
| nameType(it, append(path, "Item")) |
| } |
| genTypes = append(genTypes, &newType{ |
| name: nm, |
| typ: t, |
| kind: "and", |
| items: t.Items, |
| line: t.Line, |
| }) |
| return nm |
| case "literal": |
| nm := nameFromPath("Lit", path) |
| typeNames[t] = nm |
| for _, p := range t.Value.(ParseLiteral).Properties { |
| nameType(p.Type, append(path, p.Name)) |
| } |
| genTypes = append(genTypes, &newType{ |
| name: nm, |
| typ: t, |
| kind: "literal", |
| properties: t.Value.(ParseLiteral).Properties, |
| line: t.Line, |
| }) |
| return nm |
| case "tuple": |
| nm := nameFromPath("Tuple", path) |
| typeNames[t] = nm |
| for _, it := range t.Items { |
| nameType(it, append(path, "Item")) |
| } |
| genTypes = append(genTypes, &newType{ |
| name: nm, |
| typ: t, |
| kind: "tuple", |
| items: t.Items, |
| line: t.Line, |
| }) |
| return nm |
| case "or": |
| nm := nameFromPath("Or", path) |
| typeNames[t] = nm |
| for i, it := range t.Items { |
| // these names depend on the ordering within the "or" type |
| nameType(it, append(path, fmt.Sprintf("Item%d", i))) |
| } |
| // this code handles an "or" of stringLiterals (_InitializeParams.trace) |
| names := make(map[string]int) |
| msg := "" |
| for _, it := range t.Items { |
| if line, ok := names[typeNames[it]]; ok { |
| // duplicate component names are bad |
| msg += fmt.Sprintf("lines %d %d dup, %s for %s\n", line, it.Line, typeNames[it], nm) |
| } |
| names[typeNames[it]] = t.Line |
| } |
| // this code handles an "or" of stringLiterals (_InitializeParams.trace) |
| if len(names) == 1 { |
| var solekey string |
| for k := range names { |
| solekey = k // the sole name |
| } |
| if solekey == "string" { // _InitializeParams.trace |
| typeNames[t] = "string" |
| return "string" |
| } |
| // otherwise unexpected |
| log.Printf("unexpected: single-case 'or' type has non-string key %s: %s", nm, solekey) |
| log.Fatal(msg) |
| } else if len(names) == 2 { |
| // if one of the names is null, just use the other, rather than generating an "or". |
| // This removes about 40 types from the generated code. An entry in goplsStar |
| // could be added to handle the null case, if necessary. |
| newNm := "" |
| sawNull := false |
| for k := range names { |
| if k == "null" { |
| sawNull = true |
| } else { |
| newNm = k |
| } |
| } |
| if sawNull { |
| typeNames[t] = newNm |
| return newNm |
| } |
| } |
| genTypes = append(genTypes, &newType{ |
| name: nm, |
| typ: t, |
| kind: "or", |
| items: t.Items, |
| line: t.Line, |
| }) |
| return nm |
| case "stringLiteral": // a single type, like 'kind' or 'rename' |
| typeNames[t] = "string" |
| return "string" |
| default: |
| log.Fatalf("nameType: %T unexpected, line:%d path:%v", t, t.Line, path) |
| panic("unreachable in nameType") |
| } |
| } |
| |
| func nameFromPath(prefix string, path []string) string { |
| nm := prefix + "_" + strings.Join(path, "_") |
| // methods have slashes |
| nm = strings.ReplaceAll(nm, "/", "_") |
| return nm |
| } |