@@ -11,7 +11,7 @@ import {
11
11
queryAll ,
12
12
} from 'lit-element' ;
13
13
import { classMap } from 'lit-html/directives/class-map' ;
14
- import { translate } from 'lit-translate' ;
14
+ import { get , translate } from 'lit-translate' ;
15
15
16
16
import '@material/mwc-button' ;
17
17
import '@material/mwc-icon' ;
@@ -22,12 +22,14 @@ import '@material/mwc-checkbox';
22
22
import { Button } from '@material/mwc-button' ;
23
23
import { Checkbox } from '@material/mwc-checkbox' ;
24
24
import { List , MWCListIndex } from '@material/mwc-list' ;
25
+ import { ListItem } from '@material/mwc-list/mwc-list-item.js' ;
25
26
26
27
import '../../filtered-list.js' ;
27
28
28
29
import {
29
30
identity ,
30
31
isPublic ,
32
+ newLogEvent ,
31
33
newSubWizardEvent ,
32
34
newActionEvent ,
33
35
} from '../../foundation.js' ;
@@ -84,7 +86,7 @@ export class CleanupDataTypes extends LitElement {
84
86
cleanupList : List | undefined ;
85
87
86
88
@queryAll ( 'mwc-check-list-item.cleanup-list-item' )
87
- cleanupListItems : NodeList | undefined ;
89
+ cleanupListItems : ListItem [ ] | undefined ;
88
90
89
91
@query ( '.clean-sub-types-checkbox' )
90
92
cleanSubTypesCheckbox : Checkbox | undefined ;
@@ -222,23 +224,20 @@ export class CleanupDataTypes extends LitElement {
222
224
223
225
/**
224
226
* Recurses through all datatype templates and indexes their usage.
225
- * @returns a map of data type templates usage.
227
+ * @returns a map of data type templates usage by id .
226
228
*/
227
- private indexDataTypeTemplates ( ) {
228
- const dataTypeTemplates = this . doc . querySelector (
229
- ':root > DataTypeTemplates'
230
- ) ! ;
231
- const allUsages = this . fetchTree ( dataTypeTemplates ) ;
229
+ private indexDataTypeTemplates ( dttStart : Element [ ] ) {
232
230
const dataTypeFrequencyUsage = new Map < string , number > ( ) ;
231
+
232
+ // recursively fetch all usages
233
+ const allUsages = this . fetchTree ( dttStart ) ;
234
+
233
235
// make frequency count of datatype ids
234
236
allUsages . forEach ( item => {
235
- const itemType = item ! . getAttribute ( 'id' ) ;
236
- if ( itemType !== null ) {
237
- dataTypeFrequencyUsage . set (
238
- itemType ! ,
239
- ( dataTypeFrequencyUsage . get ( itemType ! ) || 0 ) + 1
240
- ) ;
241
- }
237
+ dataTypeFrequencyUsage . set (
238
+ item ,
239
+ ( dataTypeFrequencyUsage . get ( item ) || 0 ) + 1
240
+ ) ;
242
241
} ) ;
243
242
return dataTypeFrequencyUsage ;
244
243
}
@@ -248,16 +247,22 @@ export class CleanupDataTypes extends LitElement {
248
247
* @param element - the SCL Element for which a datatype is required.
249
248
* @returns either the datatype or null.
250
249
*/
251
- private getSubTypes ( element : Element ) : Element | null {
250
+ private getSubType ( element : Element ) : Element | null {
252
251
const dataTypeTemplates = this . doc . querySelector (
253
252
':root > DataTypeTemplates'
254
253
) ;
255
254
const type = element . getAttribute ( 'type' ) ;
256
255
if ( element . tagName === 'DO' || element . tagName === 'SDO' ) {
257
256
return dataTypeTemplates ! . querySelector ( `DOType[id="${ type } "]` ) ;
258
- } else if ( ( element . tagName === 'DA' || element . tagName === 'BDA' ) && element . getAttribute ( 'bType' ) === 'Struct' ) {
257
+ } else if (
258
+ ( element . tagName === 'DA' || element . tagName === 'BDA' ) &&
259
+ element . getAttribute ( 'bType' ) === 'Struct'
260
+ ) {
259
261
return dataTypeTemplates ! . querySelector ( `DAType[id="${ type } "]` ) ;
260
- } else if ( ( element . tagName === 'DA' || element . tagName === 'BDA' ) && element . getAttribute ( 'bType' ) === 'Enum' ) {
262
+ } else if (
263
+ ( element . tagName === 'DA' || element . tagName === 'BDA' ) &&
264
+ element . getAttribute ( 'bType' ) === 'Enum'
265
+ ) {
261
266
return dataTypeTemplates ! . querySelector ( `EnumType[id="${ type } "]` ) ;
262
267
}
263
268
return null ;
@@ -266,29 +271,43 @@ export class CleanupDataTypes extends LitElement {
266
271
/**
267
272
* Recurses from an initial element to find all child references (with duplicates).
268
273
* @param rootElement - root SCL Element for which all child datatype references are required.
269
- * @returns all SCL element datatypes traversed.
274
+ * @returns the id value for all SCL element datatypes traversed.
270
275
*/
271
- private fetchTree ( rootElement : Element ) : ( Element | undefined ) [ ] {
272
- const elementStack = [ rootElement ] ;
273
- const traversedElements : ( Element | undefined ) [ ] = [ ] ;
276
+ private fetchTree ( rootElements : Element [ ] ) : string [ ] {
277
+ const elementStack = [ ... rootElements ] ;
278
+ const traversedElements : string [ ] = [ ] ;
274
279
275
280
// A max stack depth is defined to avoid recursive references.
276
- const MAX_STACK_DEPTH = 10000 ;
277
- while ( elementStack . length > 0 && elementStack . length < MAX_STACK_DEPTH ) {
281
+ const MAX_STACK_DEPTH = 300000 ;
282
+
283
+ while ( elementStack . length > 0 && elementStack . length <= MAX_STACK_DEPTH ) {
278
284
const currentElement = elementStack . pop ( ) ;
279
- traversedElements . push ( currentElement ) ;
285
+ traversedElements . push ( currentElement ! . getAttribute ( 'id' ) ! ) ;
286
+
287
+ const selector = 'DO, SDO, DA, BDA' ;
280
288
281
- Array . from ( currentElement ! . querySelectorAll ( 'DO, SDO, DA, BDA' ) )
289
+ Array . from ( currentElement ! . querySelectorAll ( selector ) )
282
290
. filter ( isPublic )
283
291
. forEach ( element => {
284
- const newElements = this . getSubTypes ( element ) ;
285
- if ( newElements !== null ) {
286
- elementStack . unshift ( newElements ) ;
292
+ const newElement = this . getSubType ( element ) ;
293
+ if ( newElement !== null ) {
294
+ elementStack . unshift ( newElement ) ;
287
295
}
288
296
} ) ;
297
+
298
+ if ( elementStack . length >= MAX_STACK_DEPTH ) {
299
+ this . dispatchEvent (
300
+ newLogEvent ( {
301
+ kind : 'error' ,
302
+ title : get ( 'cleanup.unreferencedDataTypes.title' ) ,
303
+ message : get ( 'cleanup.unreferencedDataTypes.stackExceeded' , {
304
+ maxStackDepth : MAX_STACK_DEPTH . toString ( ) ,
305
+ } ) ,
306
+ } )
307
+ ) ;
308
+ }
289
309
}
290
- // remove root element - it is not a datatype itself
291
- traversedElements . shift ( ) ;
310
+
292
311
return traversedElements ;
293
312
}
294
313
@@ -305,26 +324,47 @@ export class CleanupDataTypes extends LitElement {
305
324
const dataTypeTemplates = this . doc . querySelector (
306
325
':root > DataTypeTemplates'
307
326
) ! ;
308
- const dataTypeUsageCounter = this . indexDataTypeTemplates ( ) ;
309
327
310
- // update index to allow checking for no longer used DataTypeTemplates
328
+ const startingLNodeTypes = Array . from (
329
+ dataTypeTemplates . querySelectorAll ( 'LNodeType' )
330
+ ) ;
331
+ const dataTypeUsageCounter =
332
+ this . indexDataTypeTemplates ( startingLNodeTypes ) ;
333
+
334
+ /* Create usage counter for children of LNodeTypes that are used.
335
+ We remember that _all_ valid template usages within a project
336
+ stem from LNodeTypes. */
311
337
cleanItems . forEach ( item => {
312
- const childDataTypeTemplates = this . fetchTree ( item ) ;
313
- childDataTypeTemplates . forEach ( element => {
314
- const type = element ! . getAttribute ( 'id' ) ! ;
315
- if ( dataTypeUsageCounter ! . has ( type ) ) {
316
- dataTypeUsageCounter ?. set (
317
- type ,
318
- dataTypeUsageCounter . get ( type ) ! - 1
319
- ) ;
320
- }
321
- } ) ;
338
+ if ( item . tagName === 'LNodeType' ) {
339
+ const childDataTypeTemplateIds = this . fetchTree ( [ item ] ) ;
340
+ childDataTypeTemplateIds . forEach ( id => {
341
+ dataTypeUsageCounter ?. set ( id , dataTypeUsageCounter . get ( id ) ! - 1 ) ;
342
+ } ) ;
343
+ }
322
344
} ) ;
323
345
324
- // locate from index consequentially unused DataTypeTemplates
325
- // and add to items to be removed
346
+ /* Check to see if children of unused DOType, DAType are present
347
+ If so then unless they are from a data type which is part of
348
+ the main usage counter they can be safely removed.
349
+ If they are part of the main usage counter, then this does not
350
+ need to be considered as these DOType and DAType elements are
351
+ dangling, they're usage is not relevant. */
352
+ cleanItems . forEach ( item => {
353
+ if ( [ 'DOType' , 'DAType' ] . includes ( item . tagName ) ) {
354
+ const unusedDataTypeTemplateChildrenIds = uniq (
355
+ this . fetchTree ( [ item ] )
356
+ ) ;
357
+ unusedDataTypeTemplateChildrenIds . forEach ( id => {
358
+ if ( dataTypeUsageCounter . get ( < string > id ) === undefined )
359
+ cleanItems . push ( dataTypeTemplates . querySelector ( `[id="${ id } "]` ) ! ) ;
360
+ } ) ;
361
+ }
362
+ } ) ;
363
+
364
+ /* Now go through our usage index. If usage is zero then we can
365
+ remove the data type template safely. */
326
366
dataTypeUsageCounter ?. forEach ( ( count , dataTypeId ) => {
327
- if ( count == = 0 ) {
367
+ if ( count < = 0 ) {
328
368
cleanItems . push (
329
369
dataTypeTemplates . querySelector ( `[id="${ dataTypeId } "]` ) !
330
370
) ;
@@ -354,6 +394,9 @@ export class CleanupDataTypes extends LitElement {
354
394
dataTypeItemsDeleteActions . forEach ( deleteAction =>
355
395
this . dispatchEvent ( newActionEvent ( deleteAction ) )
356
396
) ;
397
+ this . cleanupListItems ! . forEach ( ( item ) => {
398
+ item . selected = false ;
399
+ } ) ;
357
400
} }
358
401
> </ mwc- butto n> ` ;
359
402
}
@@ -532,18 +575,18 @@ export class CleanupDataTypes extends LitElement {
532
575
opacity : 1 ;
533
576
}
534
577
578
+ /* Make sure to type filter here
579
+ .hidden is set on string filter in filtered-list and must always filter*/
580
+ .cleanup-list-item .hiddenontypefilter : not (.hidden ) {
581
+ display : none;
582
+ }
583
+
535
584
/* filter disabled, Material Design guidelines for opacity */
536
585
.t-da-type-filter ,
537
586
.t-enum-type-filter ,
538
587
.t-lnode-type-filter ,
539
588
.t-do-type-filter {
540
589
opacity : 0.38 ;
541
590
}
542
-
543
- /* Make sure to type filter here
544
- .hidden is set on string filter in filtered-list and must always filter*/
545
- .cleanupListItem .hiddenontypefilter : not (.hidden ) {
546
- display : none;
547
- }
548
591
` ;
549
592
}
0 commit comments