All files / app/codeCharta/util deltaGenerator.ts

100% Statements 67/67
100% Branches 28/28
100% Functions 9/9
100% Lines 67/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 16383x 83x 83x 83x 83x 83x   83x     5x 47x 5x   42x 42x     5x       5x 5x 5x 5x       5x 5x   5x   5x   5x       5x 5x 36x   5x               5x 38x 38x 27x 10x   27x 27x 27x         27x 27x   11x 1x   11x   11x             38x 38x 38x         5x 9x 1x   9x 9x           9x 19x 19x     9x         31x 31x   31x 31x 47x     31x 57x 57x   57x 33x     57x 57x           31x       5x                       5x                              
import { CCFile, CodeMapNode, FileMeta, KeyValuePair, NodeType } from "../codeCharta.model"
import { FileNameHelper } from "./fileNameHelper"
import { hierarchy } from "d3-hierarchy"
import packageJson from "../../../package.json"
import { getParent } from "./nodePathHelper"
import { fileRoot } from "../services/loadFile/fileRoot"
 
export class DeltaGenerator {
    static createCodeMapFromHashMap(hashMapWithAllNodes: Map<string, CodeMapNode>) {
        let rootNode: CodeMapNode
        for (const [path, node] of hashMapWithAllNodes) {
            if (path === fileRoot.rootPath) {
                rootNode = node
            } else {
                const parentNode = getParent(hashMapWithAllNodes, path)
                parentNode.children.push(node)
            }
        }
        return rootNode
    }
 
    static getDeltaFile(referenceFile: CCFile, comparisonFile: CCFile) {
        const deltaNodesByPath = this.getDeltaNodesByPath(referenceFile.map, comparisonFile.map)
        const map = this.createCodeMapFromHashMap(deltaNodesByPath)
        const fileMeta = this.getFileMetaData(referenceFile, comparisonFile)
        return this.getNewCCFileWithDeltas(map, fileMeta)
    }
 
    private static getDeltaNodesByPath(referenceMap: CodeMapNode, comparisonMap: CodeMapNode) {
        const deltaNodesByPath = new Map<string, CodeMapNode>()
        const referenceNodesByPath = this.getReferenceNodesByPath(referenceMap)
 
        this.addExistingAndNewNodesToDeltaMap(referenceNodesByPath, comparisonMap, deltaNodesByPath)
 
        this.addDeletedNodesToDeltaMap(referenceNodesByPath, deltaNodesByPath)
 
        return deltaNodesByPath
    }
 
    private static getReferenceNodesByPath(referenceMap: CodeMapNode) {
        const referenceNodesByPath = new Map<string, CodeMapNode>()
        for (const { data } of hierarchy(referenceMap)) {
            referenceNodesByPath.set(data.path, data)
        }
        return referenceNodesByPath
    }
 
    private static addExistingAndNewNodesToDeltaMap(
        referenceNodesByPath: Map<string, CodeMapNode>,
        comparisonMap: CodeMapNode,
        deltaNodesByPath: Map<string, CodeMapNode>
    ) {
        for (const { data: comparisonNode } of hierarchy(comparisonMap)) {
            const referenceNode = referenceNodesByPath.get(comparisonNode.path)
            if (referenceNode) {
                if (referenceNode.children || comparisonNode.children) {
                    referenceNode.children = []
                }
                const { deltaList, differenceExists } = this.compareAttributeValues(referenceNode.attributes, comparisonNode.attributes)
                referenceNode.deltas = deltaList
                const changed = differenceExists ? 1 : 0
                // TODO: The attributes have to be consolidated to have a single set of
                // attributes instead of conflicting attributes. This applies to all
                // attributes and is not specific about the attributes from the
                // reference node.
                referenceNode.attributes = comparisonNode.attributes
                referenceNode.fileCount = { added: 0, removed: 0, changed }
            } else {
                if (comparisonNode.children) {
                    comparisonNode.children = []
                }
                comparisonNode.deltas = { ...comparisonNode.attributes }
 
                comparisonNode.fileCount = {
                    added: comparisonNode.type === NodeType.FILE ? 1 : 0,
                    removed: 0,
                    changed: 0
                }
            }
 
            const node = referenceNode ?? comparisonNode
            deltaNodesByPath.set(node.path, node)
            referenceNodesByPath.delete(node.path)
        }
    }
 
    private static addDeletedNodesToDeltaMap(referenceNodesByPath: Map<string, CodeMapNode>, deltaNodesByPath: Map<string, CodeMapNode>) {
        for (const node of referenceNodesByPath.values()) {
            if (node.children) {
                node.children = []
            }
            node.deltas = {}
            node.fileCount = {
                added: 0,
                removed: node.type === NodeType.FILE ? 1 : 0,
                changed: 0
            }
 
            for (const [key, value] of Object.entries(node.attributes)) {
                node.deltas[key] = -value
                node.attributes[key] = 0
            }
 
            deltaNodesByPath.set(node.path, node)
        }
    }
 
    private static compareAttributeValues(reference: KeyValuePair, comparison: KeyValuePair) {
        const deltaList: KeyValuePair = {}
        let differenceExists = false
 
        const attributeKeys = new Set(Object.keys(reference))
        for (const key of Object.keys(comparison)) {
            attributeKeys.add(key)
        }
 
        for (const key of attributeKeys) {
            const referenceAttribute = reference[key] ?? 0
            const compAttribute = comparison[key] ?? 0
 
            if (referenceAttribute !== compAttribute) {
                differenceExists = true
            }
 
            const attributeDelta = compAttribute - referenceAttribute
            deltaList[key] = attributeDelta
        }
 
        // TODO: All entries should have the combined attributes and deltas set,
        // even if they do not exist on one side. Calculate these attributes up
        // front. This operation is otherwise costly.
        return { deltaList, differenceExists }
    }
 
    private static getFileMetaData(referenceFile: CCFile, comparisonFile: CCFile): FileMeta {
        return {
            fileName: `delta_between_${FileNameHelper.withoutCCExtension(
                referenceFile.fileMeta.fileName
            )}_and_${FileNameHelper.withoutCCExtension(comparisonFile.fileMeta.fileName)}`,
            fileChecksum: `${referenceFile.fileMeta.fileChecksum};${comparisonFile.fileMeta.fileChecksum}`,
            apiVersion: packageJson.codecharta.apiVersion,
            projectName: `delta_between_${referenceFile.fileMeta.projectName}_and_${comparisonFile.fileMeta.projectName}`,
            exportedFileSize: referenceFile.fileMeta.exportedFileSize + comparisonFile.fileMeta.exportedFileSize
        }
    }
 
    private static getNewCCFileWithDeltas(rootNode: CodeMapNode, fileMeta: FileMeta): CCFile {
        return {
            map: rootNode,
            fileMeta,
            settings: {
                fileSettings: {
                    edges: [],
                    blacklist: [],
                    attributeTypes: { nodes: {}, edges: {} },
                    attributeDescriptors: {},
                    markedPackages: []
                }
            }
        }
    }
}