1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License. See License.txt in the project root for license information.
3
4package delta
5
6import (
7	"sort"
8
9	"github.com/Azure/azure-sdk-for-go/tools/internal/exports"
10)
11
12// Content defines the set of exported constants, funcs, and structs.
13type Content struct {
14	exports.Content
15
16	// contains the names of structs that are modified in whole (i.e. new/removed)
17	CompleteStructs []string `json:"newStructs,omitempty"`
18}
19
20// Count returns the count of items
21func (c Content) Count() int {
22	return c.Content.Count() + len(c.CompleteStructs)
23}
24
25// NewContent returns an initialized Content object.
26func NewContent() Content {
27	return Content{
28		Content: exports.NewContent(),
29	}
30}
31
32// GetModifiedStructs returns the subset, if any, of structs that are modified.
33func (c Content) GetModifiedStructs() map[string]exports.Struct {
34	if len(c.CompleteStructs) == 0 {
35		return c.Structs
36	}
37	ms := map[string]exports.Struct{}
38	for k, v := range c.Structs {
39		if contains(c.CompleteStructs, k) {
40			continue
41		}
42		ms[k] = v
43	}
44	return ms
45}
46
47// returns true if sl contains x
48func contains(sl []string, x string) bool {
49	for _, s := range sl {
50		if s == x {
51			return true
52		}
53	}
54	return false
55}
56
57// GetExports returns a exports.Content struct containing all exports in rhs that aren't in lhs.
58// This includes any new fields added to structs or methods added to interfaces.
59func GetExports(lhs, rhs exports.Content) Content {
60	nc := NewContent()
61
62	for n, v := range rhs.Consts {
63		if _, ok := lhs.Consts[n]; !ok {
64			nc.Consts[n] = v
65		}
66	}
67
68	for n, v := range rhs.Funcs {
69		if _, ok := lhs.Funcs[n]; !ok {
70			nc.Funcs[n] = v
71		}
72	}
73
74	for n, v := range rhs.Interfaces {
75		if _, ok := lhs.Interfaces[n]; !ok {
76			nc.Interfaces[n] = v
77		}
78	}
79
80	for n, v := range rhs.Structs {
81		if _, ok := lhs.Structs[n]; !ok {
82			nc.Structs[n] = v
83			nc.CompleteStructs = append(nc.CompleteStructs, n)
84		}
85	}
86
87	structFields := GetStructFields(lhs, rhs)
88	if len(structFields) > 0 {
89		for k, v := range structFields {
90			nc.Structs[k] = v
91		}
92	}
93
94	intMethods := GetInterfaceMethods(lhs, rhs)
95	if len(intMethods) > 0 {
96		for k, v := range intMethods {
97			nc.Interfaces[k] = v
98		}
99	}
100
101	sort.Strings(nc.CompleteStructs)
102	return nc
103}
104
105// GetStructFields returns structs common to lhs and rhs where structs in rhs contain
106// fields not in lhs.  Key is the struct type name, value contains the added content.
107func GetStructFields(lhs, rhs exports.Content) map[string]exports.Struct {
108	nf := map[string]exports.Struct{}
109
110	for rhsKey, rhsVal := range rhs.Structs {
111		if lhsStruct, ok := lhs.Structs[rhsKey]; ok {
112			nc := exports.Struct{}
113			for _, rhsAnon := range rhsVal.AnonymousFields {
114				found := false
115				for _, lhsAnon := range lhsStruct.AnonymousFields {
116					if lhsAnon == rhsAnon {
117						found = true
118						break
119					}
120				}
121				if !found {
122					nc.AnonymousFields = append(nc.AnonymousFields, rhsAnon)
123				}
124			}
125			for fn, fv := range rhsVal.Fields {
126				if _, ok := lhsStruct.Fields[fn]; !ok {
127					if nc.Fields == nil {
128						nc.Fields = map[string]string{}
129					}
130					nc.Fields[fn] = fv
131				}
132			}
133			// only add it if there's new content
134			if len(nc.AnonymousFields) > 0 || len(nc.Fields) > 0 {
135				nf[rhsKey] = nc
136			}
137		}
138	}
139	return nf
140}
141
142// GetInterfaceMethods returns interfaces common to lhs and rhs where interfaces in rhs contain
143// methods not in lhs.  Key is the interface type name, value contains the added content.
144func GetInterfaceMethods(lhs, rhs exports.Content) map[string]exports.Interface {
145	ni := map[string]exports.Interface{}
146
147	for rhsKey, rhsValue := range rhs.Interfaces {
148		if lhsInterface, ok := lhs.Interfaces[rhsKey]; ok {
149			nc := exports.Interface{}
150			for in, iv := range rhsValue.Methods {
151				if _, ok := lhsInterface.Methods[in]; !ok {
152					if nc.Methods == nil {
153						nc.Methods = map[string]exports.Func{}
154					}
155					nc.Methods[in] = iv
156				}
157			}
158			// only add it if there's new content
159			if len(nc.Methods) > 0 {
160				ni[rhsKey] = nc
161			}
162		}
163	}
164	return ni
165}
166
167// Signature contains the details of how a type signature changed (e.g. From:"int" To:"string").
168type Signature struct {
169	// From contains the original signature.
170	From string `json:"from"`
171
172	// To contains the new signature.
173	To string `json:"to"`
174}
175
176// GetConstTypeChanges returns a collection of const where the type has changed.
177// Key is the const name, value contains the type change information.
178func GetConstTypeChanges(lhs, rhs exports.Content) map[string]Signature {
179	cc := map[string]Signature{}
180
181	for rhsKey, rhsValue := range rhs.Consts {
182		if _, ok := lhs.Consts[rhsKey]; !ok {
183			continue
184		}
185		if lhs.Consts[rhsKey].Type != rhsValue.Type {
186			cc[rhsKey] = Signature{
187				From: lhs.Consts[rhsKey].Type,
188				To:   rhsValue.Type,
189			}
190		}
191	}
192	return cc
193}
194
195// None is the value used for functions with no parameters and/or no return values.
196const None = "<none>"
197
198// FuncSig contains the details of how a function's signature changed.
199type FuncSig struct {
200	// Params contains the parameter signature changes, may be nil.
201	Params *Signature `json:"params,omitempty"`
202
203	// Returns contains the return signature changes, may be nil.
204	Returns *Signature `json:"returns,omitempty"`
205}
206
207func (fs FuncSig) isEmpty() bool {
208	return fs.Params == nil && fs.Returns == nil
209}
210
211// GetFuncSigChanges returns a collection of functions that contain signature changes (params and/or returns).
212// Key is the function name, value contains the signature change information.
213func GetFuncSigChanges(lhs, rhs exports.Content) map[string]FuncSig {
214	fsc := map[string]FuncSig{}
215
216	for rhsKey, rhsValue := range rhs.Funcs {
217		if _, ok := lhs.Funcs[rhsKey]; !ok {
218			continue
219		}
220		sig := FuncSig{}
221		if !safeStrCmp(lhs.Funcs[rhsKey].Params, rhsValue.Params) {
222			sig.Params = &Signature{
223				From: safeFuncSig(lhs.Funcs[rhsKey].Params),
224				To:   safeFuncSig(rhsValue.Params),
225			}
226		}
227		if !safeStrCmp(lhs.Funcs[rhsKey].Returns, rhsValue.Returns) {
228			sig.Returns = &Signature{
229				From: safeFuncSig(lhs.Funcs[rhsKey].Returns),
230				To:   safeFuncSig(rhsValue.Returns),
231			}
232		}
233
234		if !sig.isEmpty() {
235			fsc[rhsKey] = sig
236		}
237	}
238	return fsc
239}
240
241// InterfaceDef contains a collection of interface methods with signature changes.
242// Key is the method name, value contains the signature change information.
243type InterfaceDef struct {
244	MethodSigs map[string]FuncSig `json:"funcSig"`
245}
246
247// GetInterfaceMethodSigChanges returns a collection of interfaces with method signature changes.
248// Key is the interface name, value contains the method signature change information.
249func GetInterfaceMethodSigChanges(lhs, rhs exports.Content) map[string]InterfaceDef {
250	isc := map[string]InterfaceDef{}
251
252	for rhsKey, rhsValue := range rhs.Interfaces {
253		if _, ok := lhs.Interfaces[rhsKey]; !ok {
254			continue
255		}
256		id := InterfaceDef{}
257
258		for rhsMethod, rhsSig := range rhsValue.Methods {
259			if _, ok := lhs.Interfaces[rhsKey].Methods[rhsMethod]; !ok {
260				continue
261			}
262			sig := FuncSig{}
263			if !safeStrCmp(lhs.Interfaces[rhsKey].Methods[rhsMethod].Params, rhsSig.Params) {
264				sig.Params = &Signature{
265					From: safeFuncSig(lhs.Interfaces[rhsKey].Methods[rhsMethod].Params),
266					To:   safeFuncSig(rhsSig.Params),
267				}
268			}
269			if !safeStrCmp(lhs.Interfaces[rhsKey].Methods[rhsMethod].Returns, rhsSig.Returns) {
270				sig.Returns = &Signature{
271					From: safeFuncSig(lhs.Interfaces[rhsKey].Methods[rhsMethod].Returns),
272					To:   safeFuncSig(rhsSig.Returns),
273				}
274			}
275
276			if !sig.isEmpty() {
277				if id.MethodSigs == nil {
278					id.MethodSigs = map[string]FuncSig{}
279				}
280				id.MethodSigs[rhsMethod] = sig
281			}
282		}
283
284		if len(id.MethodSigs) > 0 {
285			isc[rhsKey] = id
286		}
287	}
288	return isc
289}
290
291// StructDef contains a collection of fields within a struct where the field's type has changed.
292// Key is the field name, value contains the signature change information.
293type StructDef struct {
294	Fields map[string]Signature `json:"fields"`
295}
296
297// GetStructFieldChanges returns a collection of structs with fields that changed their type.
298// Key is the struct name, value contains fields with signature changes.
299func GetStructFieldChanges(lhs, rhs exports.Content) map[string]StructDef {
300	sfc := map[string]StructDef{}
301
302	for rhsKey, rhsValue := range rhs.Structs {
303		if _, ok := lhs.Structs[rhsKey]; !ok {
304			continue
305		}
306		sd := StructDef{}
307
308		for rhsField, rhsSig := range rhsValue.Fields {
309			if _, ok := lhs.Structs[rhsKey].Fields[rhsField]; !ok {
310				continue
311			}
312			if lhs.Structs[rhsKey].Fields[rhsField] != rhsSig {
313				if sd.Fields == nil {
314					sd.Fields = map[string]Signature{}
315				}
316				sd.Fields[rhsField] = Signature{
317					From: lhs.Structs[rhsKey].Fields[rhsField],
318					To:   rhsSig,
319				}
320			}
321		}
322
323		if len(sd.Fields) > 0 {
324			sfc[rhsKey] = sd
325		}
326	}
327	return sfc
328}
329
330func safeFuncSig(s *string) string {
331	if s == nil {
332		return None
333	}
334	return *s
335}
336
337func safeStrCmp(lhs, rhs *string) bool {
338	if lhs == nil && rhs == nil {
339		return true
340	}
341	if lhs == nil || rhs == nil {
342		return false
343	}
344	return *lhs == *rhs
345}
346