11const visit = require ( 'unist-util-visit' )
2+ const visitWithAncestors = require ( 'unist-util-visit-parents' )
23const NUMBER_REGEX = / ^ [ 0 - 9 ] + $ /
34
45// Converts conventional commit AST into conventional-changelog's
56// output format, see: https://www.npmjs.com/package/conventional-commits-parser
67function toConventionalChangelogFormat ( ast ) {
78 const cc = {
8- body : null ,
9+ body : '' ,
10+ subject : '' ,
11+ type : '' ,
12+ scope : null ,
913 notes : [ ] ,
1014 references : [ ] ,
1115 mentions : [ ] ,
1216 merge : null ,
13- revert : null
17+ revert : null ,
18+ header : '' ,
19+ footer : null
1420 }
15- let breaking
16- let body
17- let summary
1821 // Separate the body and summary nodes, this simplifies the subsequent
1922 // tree walking logic:
23+ let body
24+ let summary
2025 visit ( ast , [ 'body' , 'summary' ] , ( node ) => {
2126 switch ( node . type ) {
2227 case 'body' :
@@ -25,54 +30,77 @@ function toConventionalChangelogFormat (ast) {
2530 case 'summary' :
2631 summary = node
2732 break
28- default :
29- break
3033 }
3134 } )
3235
33- visit ( summary , ( ) => true , ( node ) => {
36+ // <type>, "(", <scope>, ")", ["!"], ":", <whitespace>*, <text>
37+ visit ( summary , ( node ) => {
3438 switch ( node . type ) {
3539 case 'type' :
3640 cc . type = node . value
41+ cc . header += node . value
3742 break
3843 case 'scope' :
3944 cc . scope = node . value
45+ cc . header += `(${ node . value } )`
46+ break
47+ case 'breaking-change' :
48+ cc . header += '!'
4049 break
4150 case 'text' :
4251 cc . subject = node . value
43- break
44- case 'breaking-change' :
45- breaking = {
46- title : 'BREAKING CHANGE'
47- // "text" node should be added with subject after walk.
48- }
52+ cc . header += `: ${ node . value } `
4953 break
5054 default :
5155 break
5256 }
5357 } )
54- // The header contains the recombined components of the summary:
55- cc . header = `${ cc . type } ${ cc . scope ? `(${ cc . scope } )` : '' } ${ breaking ? '!' : '' } : ${ cc . subject } `
5658
59+ // [<any body-text except pre-footer>]
5760 if ( body ) {
58- let text = ''
5961 visit ( body , 'text' , ( node , _i , parent ) => {
60- if ( parent . type !== 'body' ) return
61- if ( text !== '' ) text += '\n'
62- text += node . value
62+ // TODO(@bcoe): once we have \n tokens in tree we can drop this:
63+ if ( cc . body !== '' ) cc . body += '\n'
64+ cc . body += node . value
6365 } )
64- if ( text !== '' ) cc . body = text
6566 }
6667
67- // A breaking change note was found either in the body, the header, or
68- // in one of the footers:
69- // TODO(bcoe): if we refactor the grammar slightly, so that footer is a
70- // direct parent of `breaking-change`, the logic for extracting a breaking
71- // change would be easier.
72- if ( breaking ) {
73- if ( ! breaking . text ) breaking . text = cc . subject
74- cc . notes . push ( breaking )
68+ // Extract BREAKING CHANGE notes, regardless of whether they fall in
69+ // summary, body, or footer:
70+ const breaking = {
71+ title : 'BREAKING CHANGE' ,
72+ text : '' // "text" will be populated if a BREAKING CHANGE token is parsed.
7573 }
74+ visitWithAncestors ( ast , [ 'breaking-change' ] , ( node , ancestors ) => {
75+ let parent = ancestors . pop ( )
76+ let startCollecting = false
77+ switch ( parent . type ) {
78+ case 'summary' :
79+ breaking . text = cc . subject
80+ break
81+ case 'body' :
82+ breaking . text = ''
83+ // We treat text from the BREAKING CHANGE marker forward as
84+ // the breaking change notes:
85+ visit ( parent , [ 'text' , 'breaking-change' ] , ( node ) => {
86+ // TODO(@bcoe): once we have \n tokens in tree we can drop this:
87+ if ( startCollecting && node . type === 'text' ) {
88+ if ( breaking . text !== '' ) breaking . text += '\n'
89+ breaking . text += node . value
90+ } else if ( node . type === 'breaking-change' ) {
91+ startCollecting = true
92+ }
93+ } )
94+ break
95+ case 'token' :
96+ parent = ancestors . pop ( )
97+ visit ( parent , 'text' , ( node ) => {
98+ breaking . text = node . value
99+ } )
100+ break
101+ }
102+ } )
103+ if ( breaking . text !== '' ) cc . notes . push ( breaking )
76104
77105 // Populates references array from footers:
78106 // references: [{
@@ -109,8 +137,6 @@ function toConventionalChangelogFormat (ast) {
109137 reference . issue = node . value
110138 }
111139 break
112- default :
113- break
114140 }
115141 } )
116142 // TODO(@bcoe): how should references like "Refs: v8:8940" work.
0 commit comments