Skip to content

Commit 0a31aaf

Browse files
authored
fix(editors/cleanup) Cleanup of datatypes is buggy (openscd#1034)
* Deselect checkbox items after action for control blocks and datasets * Improve comments, deselect items after action, improve tests * Update snapshot * Update german translation
1 parent 26f7fe5 commit 0a31aaf

File tree

11 files changed

+314
-86
lines changed

11 files changed

+314
-86
lines changed

src/editors/cleanup/control-blocks-container.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import '@material/mwc-checkbox';
2222
import { Button } from '@material/mwc-button';
2323
import { Checkbox } from '@material/mwc-checkbox';
2424
import { List, MWCListIndex } from '@material/mwc-list';
25+
import { ListItem } from '@material/mwc-list/mwc-list-item.js';
2526

2627
import '../../filtered-list.js';
2728

@@ -96,7 +97,7 @@ export class CleanupControlBlocks extends LitElement {
9697
cleanupList: List | undefined;
9798

9899
@queryAll('mwc-check-list-item.cleanupListItem')
99-
cleanupListItems: NodeList | undefined;
100+
cleanupListItems: ListItem[] | undefined;
100101

101102
@query('.cleanupAddressCheckbox')
102103
cleanupAddressCheckbox: Checkbox | undefined;
@@ -266,6 +267,9 @@ export class CleanupControlBlocks extends LitElement {
266267
gseSmvLogReportDeleteActions.forEach(deleteAction =>
267268
e.target?.dispatchEvent(newActionEvent(deleteAction))
268269
);
270+
this.cleanupListItems!.forEach((item) => {
271+
item.selected = false;
272+
});
269273
}}
270274
></mwc-button>`;
271275
}

src/editors/cleanup/datasets-container.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ export class CleanupDatasets extends LitElement {
127127
deleteActions.forEach(deleteAction =>
128128
e.target?.dispatchEvent(newActionEvent(deleteAction))
129129
);
130+
this.dataSetItems!.forEach((item) => {
131+
item.selected = false;
132+
});
130133
}}
131134
></mwc-button>`;
132135
}

src/editors/cleanup/datatypes-container.ts

Lines changed: 95 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
queryAll,
1212
} from 'lit-element';
1313
import { classMap } from 'lit-html/directives/class-map';
14-
import { translate } from 'lit-translate';
14+
import { get, translate } from 'lit-translate';
1515

1616
import '@material/mwc-button';
1717
import '@material/mwc-icon';
@@ -22,12 +22,14 @@ import '@material/mwc-checkbox';
2222
import { Button } from '@material/mwc-button';
2323
import { Checkbox } from '@material/mwc-checkbox';
2424
import { List, MWCListIndex } from '@material/mwc-list';
25+
import { ListItem } from '@material/mwc-list/mwc-list-item.js';
2526

2627
import '../../filtered-list.js';
2728

2829
import {
2930
identity,
3031
isPublic,
32+
newLogEvent,
3133
newSubWizardEvent,
3234
newActionEvent,
3335
} from '../../foundation.js';
@@ -84,7 +86,7 @@ export class CleanupDataTypes extends LitElement {
8486
cleanupList: List | undefined;
8587

8688
@queryAll('mwc-check-list-item.cleanup-list-item')
87-
cleanupListItems: NodeList | undefined;
89+
cleanupListItems: ListItem[] | undefined;
8890

8991
@query('.clean-sub-types-checkbox')
9092
cleanSubTypesCheckbox: Checkbox | undefined;
@@ -222,23 +224,20 @@ export class CleanupDataTypes extends LitElement {
222224

223225
/**
224226
* 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.
226228
*/
227-
private indexDataTypeTemplates() {
228-
const dataTypeTemplates = this.doc.querySelector(
229-
':root > DataTypeTemplates'
230-
)!;
231-
const allUsages = this.fetchTree(dataTypeTemplates);
229+
private indexDataTypeTemplates(dttStart: Element[]) {
232230
const dataTypeFrequencyUsage = new Map<string, number>();
231+
232+
// recursively fetch all usages
233+
const allUsages = this.fetchTree(dttStart);
234+
233235
// make frequency count of datatype ids
234236
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+
);
242241
});
243242
return dataTypeFrequencyUsage;
244243
}
@@ -248,16 +247,22 @@ export class CleanupDataTypes extends LitElement {
248247
* @param element - the SCL Element for which a datatype is required.
249248
* @returns either the datatype or null.
250249
*/
251-
private getSubTypes(element: Element): Element | null {
250+
private getSubType(element: Element): Element | null {
252251
const dataTypeTemplates = this.doc.querySelector(
253252
':root > DataTypeTemplates'
254253
);
255254
const type = element.getAttribute('type');
256255
if (element.tagName === 'DO' || element.tagName === 'SDO') {
257256
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+
) {
259261
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+
) {
261266
return dataTypeTemplates!.querySelector(`EnumType[id="${type}"]`);
262267
}
263268
return null;
@@ -266,29 +271,43 @@ export class CleanupDataTypes extends LitElement {
266271
/**
267272
* Recurses from an initial element to find all child references (with duplicates).
268273
* @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.
270275
*/
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[] = [];
274279

275280
// 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) {
278284
const currentElement = elementStack.pop();
279-
traversedElements.push(currentElement);
285+
traversedElements.push(currentElement!.getAttribute('id')!);
286+
287+
const selector = 'DO, SDO, DA, BDA';
280288

281-
Array.from(currentElement!.querySelectorAll('DO, SDO, DA, BDA'))
289+
Array.from(currentElement!.querySelectorAll(selector))
282290
.filter(isPublic)
283291
.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);
287295
}
288296
});
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+
}
289309
}
290-
// remove root element - it is not a datatype itself
291-
traversedElements.shift();
310+
292311
return traversedElements;
293312
}
294313

@@ -305,26 +324,47 @@ export class CleanupDataTypes extends LitElement {
305324
const dataTypeTemplates = this.doc.querySelector(
306325
':root > DataTypeTemplates'
307326
)!;
308-
const dataTypeUsageCounter = this.indexDataTypeTemplates();
309327

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. */
311337
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+
}
322344
});
323345

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. */
326366
dataTypeUsageCounter?.forEach((count, dataTypeId) => {
327-
if (count === 0) {
367+
if (count <= 0) {
328368
cleanItems.push(
329369
dataTypeTemplates.querySelector(`[id="${dataTypeId}"]`)!
330370
);
@@ -354,6 +394,9 @@ export class CleanupDataTypes extends LitElement {
354394
dataTypeItemsDeleteActions.forEach(deleteAction =>
355395
this.dispatchEvent(newActionEvent(deleteAction))
356396
);
397+
this.cleanupListItems!.forEach((item) => {
398+
item.selected = false;
399+
});
357400
}}
358401
></mwc-button>`;
359402
}
@@ -532,18 +575,18 @@ export class CleanupDataTypes extends LitElement {
532575
opacity: 1;
533576
}
534577
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+
535584
/* filter disabled, Material Design guidelines for opacity */
536585
.t-da-type-filter,
537586
.t-enum-type-filter,
538587
.t-lnode-type-filter,
539588
.t-do-type-filter {
540589
opacity: 0.38;
541590
}
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-
}
548591
`;
549592
}

src/translations/de.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,8 @@ export const de: Translations = {
753753
tooltip:
754754
'Datentypen, die nicht in einem logischen Knoten oder einem anderen verwendeten Datentyp referenziert werden',
755755
alsoRemoveSubTypes: 'Entfernen Sie auch Untertypen',
756-
},
756+
stackExceeded: 'Maximale Aufrufe überschritten. Maximal zulässig sind {{maxStackDepth}}. Nicht alle überflüßigen Datentypen sind entfernt und das Project is potentiel beschädigt.',
757+
}
757758
},
758759
controlblock: {
759760
action: {

src/translations/en.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,8 @@ export const en = {
750750
tooltip:
751751
'Data Types which are not referenced in a Logical Node or other used Data Type',
752752
alsoRemoveSubTypes: 'Also remove subtypes',
753-
},
753+
stackExceeded: 'Max Stack Length Exceeded. Maximum allowed is {{maxStackDepth}}. Datatype cleaning incomplete and file damage may have occurred.'
754+
}
754755
},
755756
controlblock: {
756757
action: {

0 commit comments

Comments
 (0)