1
+ import * as vscode from 'vscode' ;
2
+ import * as fs from 'fs' ;
3
+ import * as path from 'path' ;
4
+ import { FileSystemUtils } from "../filesystemUtils" ;
5
+
6
+ class Component {
7
+
8
+ constructor ( tsFilename : string , templateFilename : string , selector : string , subComponents : Component [ ] , isRoot : boolean ) {
9
+ this . tsFilename = tsFilename ;
10
+ this . templateFilename = templateFilename ;
11
+ this . selector = selector ;
12
+ this . subComponents = subComponents ;
13
+ this . isRoot = isRoot ;
14
+ }
15
+ public tsFilename : string ;
16
+ public templateFilename : string ;
17
+ public selector : string ;
18
+ public subComponents : Component [ ] ;
19
+ public isRoot : boolean ;
20
+ }
21
+
22
+ class Node {
23
+ constructor ( id : string , tsFilename : string , isRoot : boolean ) {
24
+ this . id = id ;
25
+ this . tsFilename = tsFilename ;
26
+ this . isRoot = isRoot ;
27
+ }
28
+ public id : string ;
29
+ public tsFilename : string ;
30
+ public isRoot : boolean ;
31
+
32
+ public toJsonString ( ) : string {
33
+ return `{id: "${ this . id } ", label: "${ this . id } "}` ;
34
+ }
35
+ }
36
+ class Edge {
37
+ constructor ( id : string , source : string , target : string ) {
38
+ this . id = id ;
39
+ this . source = source ;
40
+ this . target = target ;
41
+ }
42
+ public id : string ;
43
+ public source : string ;
44
+ public target : string ;
45
+
46
+ public toJsonString ( ) : string {
47
+ return `{from: "${ this . source } ", to: "${ this . target } ", arrows: arrowAttr }` ;
48
+ }
49
+ }
50
+
51
+ export class ShowComponentHierarchy {
52
+
53
+ private extensionContext : vscode . ExtensionContext ;
54
+ constructor ( context : vscode . ExtensionContext ) {
55
+ this . extensionContext = context ;
56
+ }
57
+ public static get commandName ( ) : string { return 'showComponentHierarchy' ; }
58
+
59
+ public execute ( webview : vscode . Webview ) {
60
+ const fsUtils = new FileSystemUtils ( ) ;
61
+ var directoryPath : string = fsUtils . getWorkspaceFolder ( ) ;
62
+ const excludeDirectories = [ 'bin' , 'obj' , 'node_modules' , 'dist' , 'packages' , '.git' , '.vs' , '.github' ] ;
63
+ const componentFilenames = fsUtils . listFiles ( directoryPath , excludeDirectories , this . isComponentFile ) ;
64
+ const components = this . findComponents ( componentFilenames ) ;
65
+ this . enrichComponentsFromComponentTemplates ( components ) ;
66
+
67
+ let nodes : Node [ ] = [ ] ;
68
+ const appendNodes = ( nodeList : Node [ ] ) => {
69
+ nodeList . forEach ( newNode => {
70
+ if ( ! nodes . some ( node => node . id === newNode . id ) ) {
71
+ nodes = nodes . concat ( newNode ) ;
72
+ }
73
+ } ) ;
74
+ } ;
75
+ let edges : Edge [ ] = [ ] ;
76
+ const appendLinks = ( edgeList : Edge [ ] ) => {
77
+ edgeList . forEach ( newEdge => {
78
+ if ( ! edges . some ( edge => edge . source === newEdge . source && edge . target === newEdge . target ) ) {
79
+ edges = edges . concat ( newEdge ) ;
80
+ }
81
+ } ) ;
82
+ } ;
83
+ this . addNodesAndLinks ( components , appendNodes , appendLinks ) ;
84
+
85
+ const nodesJson = nodes
86
+ . map ( ( node , index , arr ) => { return node . toJsonString ( ) ; } )
87
+ . join ( ',\n' ) ;
88
+ const rootNodesJson = nodes
89
+ . filter ( node => node . isRoot )
90
+ . map ( ( node , index , arr ) => { return '"' + node . id + '"' ; } )
91
+ . join ( ',\n' ) ;
92
+ const edgesJson = edges
93
+ . map ( ( edge , index , arr ) => { return edge . toJsonString ( ) ; } )
94
+ . join ( ',\n' ) ;
95
+
96
+ try {
97
+ const jsContent = this . generateJavascriptContent ( nodesJson , rootNodesJson , edgesJson ) ;
98
+ const outputJsFilename = 'showComponentHierarchy.js' ;
99
+ let htmlContent = this . generateHtmlContent ( webview , outputJsFilename ) ;
100
+
101
+ // fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('src', 'commands', 'showComponentHierarchy.html')), htmlContent, () => { } ); // For debugging
102
+ fsUtils . writeFile (
103
+ this . extensionContext ?. asAbsolutePath ( path . join ( 'src' , 'commands' , outputJsFilename ) ) ,
104
+ jsContent ,
105
+ ( ) => {
106
+ webview . html = htmlContent ;
107
+ }
108
+ ) ;
109
+ } catch ( ex ) {
110
+ console . log ( 'Angular Tools Exception:' + ex ) ;
111
+ }
112
+ }
113
+
114
+ private isComponentFile ( filename : string ) : boolean {
115
+ return filename . endsWith ( '.component.ts' ) ;
116
+ }
117
+
118
+ private findComponents ( componentFilenames : string [ ] ) {
119
+ const compHash : { [ selector : string ] : Component ; } = { } ;
120
+ const componentRegex = / @ C o m p o n e n t \( { / ig;
121
+ const templateUrlRegex = / .* t e m p l a t e U r l : .+ \/ ( .+ ) \' / i;
122
+ const selectorRegex = / .* s e l e c t o r : .+ \' ( .+ ) \' / i;
123
+ const endBracketRegex = / } \) / i;
124
+ componentFilenames . forEach ( ( componentFilename ) => {
125
+ let componentDefinitionFound = false ;
126
+ let currentComponent = new Component ( componentFilename , "" , "" , [ ] , true ) ;
127
+ const content = fs . readFileSync ( componentFilename , 'utf8' ) ;
128
+ const lines : string [ ] = content . split ( '\n' ) ;
129
+ for ( let i : number = 0 ; i < lines . length ; i ++ ) {
130
+ let line = lines [ i ] ;
131
+ let match = componentRegex . exec ( line ) ;
132
+ if ( match ) {
133
+ componentDefinitionFound = true ;
134
+ }
135
+ if ( componentDefinitionFound ) {
136
+ match = templateUrlRegex . exec ( line ) ;
137
+ if ( match ) {
138
+ currentComponent . templateFilename = path . join ( path . dirname ( componentFilename ) , match [ 1 ] ) ;
139
+ }
140
+ match = selectorRegex . exec ( line ) ;
141
+ if ( match ) {
142
+ let currentSelector = match [ 1 ] ;
143
+ currentSelector = currentSelector . replace ( "[" , "" ) ;
144
+ currentSelector = currentSelector . replace ( "]" , "" ) ;
145
+ currentComponent . selector = currentSelector ;
146
+ }
147
+ match = endBracketRegex . exec ( line ) ;
148
+ if ( match ) {
149
+ break ;
150
+ }
151
+ }
152
+ }
153
+ compHash [ currentComponent . selector ] = currentComponent ;
154
+ } ) ;
155
+ return compHash ;
156
+ }
157
+
158
+ private enrichComponentsFromComponentTemplates ( componentHash : { [ selector : string ] : Component ; } ) {
159
+ for ( let selector1 in componentHash ) {
160
+ if ( fs . existsSync ( componentHash [ selector1 ] . templateFilename ) ) {
161
+ const template = fs . readFileSync ( componentHash [ selector1 ] . templateFilename ) ; // We read the entire template file
162
+ for ( let selector2 in componentHash ) { // then we check if the template contains each of the selectors we found in the components
163
+ let pattern = `</${ selector2 } >` ;
164
+ let index = template . indexOf ( pattern ) ;
165
+ if ( index >= 0 ) {
166
+ componentHash [ selector1 ] . subComponents = componentHash [ selector1 ] . subComponents . concat ( componentHash [ selector2 ] ) ;
167
+ // If selector2 has been found in a template then it is not root
168
+ componentHash [ selector2 ] . isRoot = false ;
169
+ }
170
+ else {
171
+ pattern = ` ${ selector2 } ` ;
172
+ index = template . indexOf ( pattern ) ;
173
+ if ( index >= 0 ) {
174
+ componentHash [ selector1 ] . subComponents = componentHash [ selector1 ] . subComponents . concat ( componentHash [ selector2 ] ) ;
175
+ // If selector2 has been found in a template then it is not root
176
+ componentHash [ selector2 ] . isRoot = false ;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ private addNodesAndLinks ( componentHash : { [ selector : string ] : Component ; } , appendNodes : ( nodeList : Node [ ] ) => void , appendLinks : ( edgeList : Edge [ ] ) => void ) {
185
+ for ( let selector in componentHash ) {
186
+ const component = componentHash [ selector ] ;
187
+ if ( component . isRoot ) {
188
+ this . generateDirectedGraphNodesXml ( component . subComponents , component , true , appendNodes ) ;
189
+ this . generateDirectedGraphLinksXml ( component . subComponents , selector , "" , appendLinks ) ;
190
+ }
191
+ }
192
+ }
193
+
194
+ private generateDirectedGraphNodesXml ( components : Component [ ] , component : Component , isRoot : boolean , appendNodes : ( nodeList : Node [ ] ) => void ) {
195
+ appendNodes ( [ new Node ( component . selector , component . templateFilename , isRoot ) ] ) ;
196
+ if ( components . length > 0 ) {
197
+ components . forEach ( ( subComponent ) => {
198
+ this . generateDirectedGraphNodesXml ( subComponent . subComponents , subComponent , subComponent . isRoot , appendNodes ) ;
199
+ } ) ;
200
+ }
201
+ }
202
+
203
+ private generateDirectedGraphLinksXml ( subComponents : Component [ ] , selector : string , parentSelector : string , appendLinks : ( edgeList : Edge [ ] ) => void ) {
204
+ if ( parentSelector . length > 0 ) {
205
+ const id = Math . random ( ) * 100000 ;
206
+ appendLinks ( [ new Edge ( id . toString ( ) , parentSelector , selector ) ] ) ;
207
+ }
208
+ if ( subComponents . length > 0 ) {
209
+ subComponents . forEach ( ( subComponent ) => {
210
+ this . generateDirectedGraphLinksXml ( subComponent . subComponents , subComponent . selector , selector , appendLinks ) ;
211
+ } ) ;
212
+ }
213
+ }
214
+
215
+ private getNonce ( ) {
216
+ let text = '' ;
217
+ const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ;
218
+ for ( let i = 0 ; i < 32 ; i ++ ) {
219
+ text += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) ;
220
+ }
221
+ return text ;
222
+ }
223
+
224
+ private generateJavascriptContent ( nodesJson : string , rootNodesJson : string , edgesJson : string ) : string {
225
+ const templateJsFilename = 'showComponentHierarchy_Template.js' ;
226
+ let template = fs . readFileSync ( this . extensionContext ?. asAbsolutePath ( path . join ( 'src' , 'commands' , templateJsFilename ) ) , 'utf8' ) ;
227
+ let jsContent = template . replace ( 'var nodes = new vis.DataSet([]);' , `var nodes = new vis.DataSet([${ nodesJson } ]);` ) ;
228
+ jsContent = jsContent . replace ( 'var rootNodes = [];' , `var rootNodes = [${ rootNodesJson } ];` ) ;
229
+ jsContent = jsContent . replace ( 'var edges = new vis.DataSet([]);' , `var edges = new vis.DataSet([${ edgesJson } ]);` ) ;
230
+ return jsContent ;
231
+ }
232
+
233
+ private generateHtmlContent ( webview : vscode . Webview , outputJsFilename : string ) : string {
234
+ const templateHtmlFilename = 'showComponentHierarchy_Template.html' ;
235
+ let htmlContent = fs . readFileSync ( this . extensionContext ?. asAbsolutePath ( path . join ( 'src' , 'commands' , templateHtmlFilename ) ) , 'utf8' ) ;
236
+
237
+ const visPath = vscode . Uri . joinPath ( this . extensionContext . extensionUri , 'src' , 'commands' , 'vis-network.min.js' ) ;
238
+ const visUri = webview . asWebviewUri ( visPath ) ;
239
+ htmlContent = htmlContent . replace ( 'vis-network.min.js' , visUri . toString ( ) ) ;
240
+
241
+ const cssPath = vscode . Uri . joinPath ( this . extensionContext . extensionUri , 'src' , 'commands' , 'showComponentHierarchy.css' ) ;
242
+ const cssUri = webview . asWebviewUri ( cssPath ) ;
243
+ htmlContent = htmlContent . replace ( 'showComponentHierarchy.css' , cssUri . toString ( ) ) ;
244
+
245
+ const nonce = this . getNonce ( ) ;
246
+ htmlContent = htmlContent . replace ( 'nonce-nonce' , `nonce-${ nonce } ` ) ;
247
+ htmlContent = htmlContent . replace ( / < s c r i p t / g, `<script nonce="${ nonce } " ` ) ;
248
+ htmlContent = htmlContent . replace ( 'cspSource' , webview . cspSource ) ;
249
+
250
+ const jsPath = vscode . Uri . joinPath ( this . extensionContext . extensionUri , 'src' , 'commands' , outputJsFilename ) ;
251
+ const jsUri = webview . asWebviewUri ( jsPath ) ;
252
+ htmlContent = htmlContent . replace ( 'showComponentHierarchy.js' , jsUri . toString ( ) ) ;
253
+ return htmlContent ;
254
+ }
255
+ }
0 commit comments