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 model
5
6import (
7	"fmt"
8	"strings"
9
10	"github.com/Azure/azure-sdk-for-go/tools/internal/delta"
11	"github.com/Azure/azure-sdk-for-go/tools/internal/markdown"
12	"github.com/Azure/azure-sdk-for-go/tools/internal/report"
13)
14
15// Changelog describes the changelog generated for a package.
16type Changelog struct {
17	// NewPackage is true if this package does not exist in the old version
18	NewPackage bool
19	// RemovedPackage is true if this package does not exist in the new version
20	RemovedPackage bool
21	// Modified contains the details of a modified package. This is nil when either NewPackage or RemovedPackage is true
22	Modified *report.Package
23}
24
25// HasBreakingChanges returns if this report of changelog contains breaking changes
26func (c Changelog) HasBreakingChanges() bool {
27	return c.RemovedPackage || (c.Modified != nil && c.Modified.HasBreakingChanges())
28}
29
30// String ...
31func (c Changelog) String() string {
32	return c.ToMarkdown()
33}
34
35// ToMarkdown returns the markdown string of this changelog
36func (c Changelog) ToMarkdown() string {
37	if c.NewPackage {
38		return ""
39	}
40	if c.RemovedPackage {
41		return "This package was removed" // this should never be executed
42	}
43	return c.Modified.ToMarkdown()
44}
45
46// ToCompactMarkdown returns the markdown string of this changelog but more compact
47func (c Changelog) ToCompactMarkdown() string {
48	if c.NewPackage {
49		return "This is a new package"
50	}
51	if c.RemovedPackage {
52		return "This package was removed"
53	}
54	return writeChangelogForPackage(c.Modified)
55}
56
57// GetBreakingChangeItems returns an array of the breaking change items
58func (c Changelog) GetBreakingChangeItems() []string {
59	if c.RemovedPackage {
60		return []string{
61			fmt.Sprintf("Package was removed"),
62		}
63	}
64	if c.Modified == nil {
65		return []string{}
66	}
67	return getBreakingChanges(c.Modified.BreakingChanges)
68}
69
70func writeChangelogForPackage(r *report.Package) string {
71	if r == nil || r.IsEmpty() {
72		return "No exported changes"
73	}
74
75	md := &markdown.Writer{}
76
77	// write breaking changes
78	md.WriteHeader("Breaking Changes")
79	for _, item := range getBreakingChanges(r.BreakingChanges) {
80		md.WriteListItem(item)
81	}
82
83	// write additional changes
84	md.WriteHeader("New Content")
85	for _, item := range getNewContents(r.AdditiveChanges) {
86		md.WriteListItem(item)
87	}
88
89	md.EmptyLine()
90	summaries := getSummaries(r.BreakingChanges, r.AdditiveChanges)
91	md.WriteLine(summaries)
92
93	return md.String()
94}
95
96func getSummaries(breaking *report.BreakingChanges, additive *delta.Content) string {
97	bc := 0
98	if breaking != nil {
99		bc = breaking.Count()
100	}
101	ac := 0
102	if additive != nil {
103		ac = additive.Count()
104	}
105
106	return fmt.Sprintf("Total %d breaking change(s), %d additive change(s).", bc, ac)
107}
108
109func getNewContents(c *delta.Content) []string {
110	if c == nil || c.IsEmpty() {
111		return nil
112	}
113
114	var items []string
115
116	if len(c.Consts) > 0 {
117		for k := range c.Consts {
118			line := fmt.Sprintf("New const `%s`", k)
119			items = append(items, line)
120		}
121	}
122	if len(c.Funcs) > 0 {
123		for k, v := range c.Funcs {
124			params := ""
125			if v.Params != nil {
126				params = *v.Params
127			}
128			returns := ""
129			if v.Returns != nil {
130				returns = *v.Returns
131				if strings.Contains(returns, ",") {
132					returns = fmt.Sprintf("(%s)", returns)
133				}
134			}
135			line := fmt.Sprintf("New function `%s(%s) %s`", k, params, returns)
136			items = append(items, line)
137		}
138	}
139	if len(c.CompleteStructs) > 0 {
140		for _, v := range c.CompleteStructs {
141			line := fmt.Sprintf("New struct `%s`", v)
142			items = append(items, line)
143		}
144	}
145	if len(c.Structs) > 0 {
146		modified := c.GetModifiedStructs()
147		for s, f := range modified {
148			for _, af := range f.AnonymousFields {
149				line := fmt.Sprintf("New anonymous field `%s` in struct `%s`", af, s)
150				items = append(items, line)
151			}
152			for f := range f.Fields {
153				line := fmt.Sprintf("New field `%s` in struct `%s`", f, s)
154				items = append(items, line)
155			}
156		}
157	}
158
159	return items
160}
161
162func getBreakingChanges(b *report.BreakingChanges) []string {
163	items := make([]string, 0)
164	if b == nil || b.IsEmpty() {
165		return items
166	}
167
168	// get signature changes
169	items = append(items, getSignatureChangeItems(b)...)
170
171	// get removed content
172	items = append(items, getRemovedContent(b.Removed)...)
173
174	return items
175}
176
177func getSignatureChangeItems(b *report.BreakingChanges) []string {
178	if b.IsEmpty() {
179		return nil
180	}
181
182	var items []string
183
184	// write const changes
185	if len(b.Consts) > 0 {
186		for k, v := range b.Consts {
187			line := fmt.Sprintf("Const `%s` type has been changed from `%s` to `%s`", k, v.From, v.To)
188			items = append(items, line)
189		}
190		// TODO -- sort?
191	}
192	// write function changes
193	if len(b.Funcs) > 0 {
194		for k, v := range b.Funcs {
195			if v.Params != nil {
196				line := fmt.Sprintf("Function `%s` parameter(s) have been changed from `(%s)` to `(%s)`", k, v.Params.From, v.Params.To)
197				items = append(items, line)
198			}
199			if v.Returns != nil {
200				line := fmt.Sprintf("Function `%s` return value(s) have been changed from `(%s)` to `(%s)`", k, v.Returns.From, v.Returns.To)
201				items = append(items, line)
202			}
203		}
204	}
205	// write struct changes
206	if len(b.Structs) > 0 {
207		for k, v := range b.Structs {
208			for f, d := range v.Fields {
209				line := fmt.Sprintf("Type of `%s.%s` has been changed from `%s` to `%s`", k, f, d.From, d.To)
210				items = append(items, line)
211			}
212		}
213	}
214	// interfaces are skipped, which are identical to some of the functions
215
216	return items
217}
218
219func getRemovedContent(removed *delta.Content) []string {
220	if removed == nil {
221		return nil
222	}
223
224	var items []string
225	// write constants
226	if len(removed.Consts) > 0 {
227		for k := range removed.Consts {
228			line := fmt.Sprintf("Const `%s` has been removed", k)
229			items = append(items, line)
230		}
231	}
232	// write functions
233	if len(removed.Funcs) > 0 {
234		for k := range removed.Funcs {
235			line := fmt.Sprintf("Function `%s` has been removed", k)
236			items = append(items, line)
237		}
238	}
239	// write complete struct removal
240	if len(removed.CompleteStructs) > 0 {
241		for _, v := range removed.CompleteStructs {
242			line := fmt.Sprintf("Struct `%s` has been removed", v)
243			items = append(items, line)
244		}
245	}
246	// write struct modification (some fields are removed)
247	modified := removed.GetModifiedStructs()
248	if len(modified) > 0 {
249		for s, f := range modified {
250			for _, af := range f.AnonymousFields {
251				line := fmt.Sprintf("Field `%s` of struct `%s` has been removed", af, s)
252				items = append(items, line)
253			}
254			for f := range f.Fields {
255				line := fmt.Sprintf("Field `%s` of struct `%s` has been removed", f, s)
256				items = append(items, line)
257			}
258		}
259	}
260
261	return items
262}
263