diff --git a/.detective/config.json b/.detective/config.json index 8d24b17..5d94096 100644 --- a/.detective/config.json +++ b/.detective/config.json @@ -1,12 +1,12 @@ { "scopes": [ - "apps/frontend", "apps/backend/src/infrastructure", "apps/backend/src/mcp", "apps/backend/src/model", "apps/backend/src/options", "apps/backend/src/services", - "apps/backend/src/utils" + "apps/backend/src/utils", + "apps/frontend" ], "groups": ["apps/backend/src", "apps/backend", "apps"], "entries": [], diff --git a/.detective/hash b/.detective/hash index 2e203b5..960ed45 100644 --- a/.detective/hash +++ b/.detective/hash @@ -1 +1 @@ -0a7d0219e81ae606e07a3ab3fedd5499f9db7f49, v1.3.0 \ No newline at end of file +1b2f59522186c242168b2282b7815a75b48d7a6b, v1.3.0 \ No newline at end of file diff --git a/apps/backend/src/services/trend-analysis/x-ray/class-metrics-analyzer.ts b/apps/backend/src/services/trend-analysis/x-ray/class-metrics-analyzer.ts index 1a3ae2c..880cb90 100644 --- a/apps/backend/src/services/trend-analysis/x-ray/class-metrics-analyzer.ts +++ b/apps/backend/src/services/trend-analysis/x-ray/class-metrics-analyzer.ts @@ -53,6 +53,7 @@ export class ClassMetricsAnalyzer extends BaseMetricsAnalyzer { const allSections = [ ...(reasonsMeta.ui.sections ?? []), ...(duplicateMeta.ui.sections ?? []), + ...(unusedMeta.ui.sections ?? []), ]; return { @@ -96,7 +97,7 @@ export class ClassMetricsAnalyzer extends BaseMetricsAnalyzer { complexity, dependencyAnalysis, reasonsMetric, - unusedMembers, + unusedMembersResult, duplicateAnalysis, ] = await Promise.all([ this.complexityMetric.analyzeAsync({ ...this.context, scopeNode: node }), @@ -143,7 +144,8 @@ export class ClassMetricsAnalyzer extends BaseMetricsAnalyzer { internalFiles: reasonsMetric.internalFiles, // Code quality metrics - unusedMembers: unusedMembers, + unusedMembers: unusedMembersResult.unusedMembers, + unusedMemberNames: unusedMembersResult.unusedMemberNames, duplicateBlocks, duplicateDetails, }; diff --git a/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.spec.ts b/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.spec.ts index 2f4405d..264ded0 100644 --- a/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.spec.ts +++ b/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.spec.ts @@ -33,14 +33,15 @@ describe('UnusedMembersMetric', () => { } `; const { sourceFile, classNode, program, checker } = buildContext(code); - const count = metric.analyze({ + const result = metric.analyze({ sourceFile, program, checker, sourceCode: code, scopeNode: classNode, }); - expect(count).toBe(5); + expect(result.unusedMembers).toBe(5); + expect(result.unusedMemberNames?.length).toBe(5); }); it('detects unused members with usage patterns', () => { @@ -61,14 +62,17 @@ describe('UnusedMembersMetric', () => { } `; const { sourceFile, classNode, program, checker } = buildContext(code); - const count = metric.analyze({ + const result = metric.analyze({ sourceFile, program, checker, sourceCode: code, scopeNode: classNode, }); - expect(count).toBe(3); + expect(result.unusedMembers).toBe(3); + expect(result.unusedMemberNames).toEqual( + expect.arrayContaining(['unused1', 'unused2', '#unusedSuper']) + ); }); it('detects unused constructor parameter properties', () => { @@ -78,14 +82,15 @@ describe('UnusedMembersMetric', () => { } `; const { sourceFile, classNode, program, checker } = buildContext(code); - const count = metric.analyze({ + const result = metric.analyze({ sourceFile, program, checker, sourceCode: code, scopeNode: classNode, }); - expect(count).toBe(1); + expect(result.unusedMembers).toBe(1); + expect(result.unusedMemberNames).toEqual(['unused']); }); it('finds no unused members when all are used or public', () => { @@ -110,13 +115,43 @@ describe('UnusedMembersMetric', () => { } `; const { sourceFile, classNode, program, checker } = buildContext(code); - const count = metric.analyze({ + const result = metric.analyze({ + sourceFile, + program, + checker, + sourceCode: code, + scopeNode: classNode, + }); + expect(result.unusedMembers).toBe(0); + expect(result.unusedMemberNames).toEqual([]); + }); + + it('does not flag private static members used via ClassName access', () => { + const code = ` + class Example { + private static readonly CONST = new Set(['a']); + private static helper() { return 'ok'; } + private value = 'v'; + + method() { + if (Example.CONST.has('a')) { + (Example as any).helper(); + } + // also ensure element access is handled + const c = (Example as any)['CONST']; + this.value = 'x'; + } + } + `; + const { sourceFile, classNode, program, checker } = buildContext(code); + const result = metric.analyze({ sourceFile, program, checker, sourceCode: code, scopeNode: classNode, }); - expect(count).toBe(0); + expect(result.unusedMembers).toBe(0); + expect(result.unusedMemberNames).toEqual([]); }); }); diff --git a/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.ts b/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.ts index 4bef7b7..54dbe1e 100644 --- a/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.ts +++ b/apps/backend/src/services/trend-analysis/x-ray/metrics/unused-members.metric.ts @@ -1,10 +1,3 @@ -import { - createWrappedNode, - ClassDeclaration as MorphClassDeclaration, - Node as MorphNode, - SyntaxKind, - ts as tsMorph, -} from 'ts-morph'; import * as ts from 'typescript'; import { z } from 'zod'; @@ -13,6 +6,11 @@ import { AnalyzerContext } from '../x-ray-metrics.types'; import { BaseMetric } from './metric.base'; import { Metric } from './metric.decorator'; +export interface UnusedMembersMetricResult { + unusedMembers: number; + unusedMemberNames: string[]; +} + @Metric({ schema: z.object({ unusedMembers: z @@ -20,6 +18,10 @@ import { Metric } from './metric.decorator'; .describe( 'Count of private and super private (#) members (including constructor parameter properties) unused within the class.' ), + unusedMemberNames: z + .array(z.string()) + .optional() + .describe('Names of the private members that are unused.'), }), ui: { metrics: [ @@ -33,127 +35,169 @@ import { Metric } from './metric.decorator'; refLink: 'https://refactoring.guru/smells/dead-code', }, ], + sections: [ + { + type: 'list', + title: 'Unused Members', + path: 'unusedMemberNames', + icon: 'list', + }, + ], }, }) -export class UnusedMembersMetric extends BaseMetric { - analyze(context: AnalyzerContext): number { +export class UnusedMembersMetric extends BaseMetric { + analyze(context: AnalyzerContext): UnusedMembersMetricResult { const targetNode = context.scopeNode; if (!targetNode || !ts.isClassDeclaration(targetNode)) { - return 0; + return { unusedMembers: 0, unusedMemberNames: [] }; } - const classDecl = createWrappedNode( - targetNode as unknown as tsMorph.Node - ).asKindOrThrow(SyntaxKind.ClassDeclaration); - - const privateMembers = this.getPrivateMembers(classDecl); - const usedMembers = this.getUsedMemberNames(classDecl); + const privateMembers = this.getPrivateMembers(targetNode); + const usedMembers = this.getUsedMemberNames(targetNode); const unusedMembers = privateMembers .filter((member) => !usedMembers.has(member.name)) .map((member) => member.name); - return unusedMembers.length; + return { + unusedMembers: unusedMembers.length, + unusedMemberNames: unusedMembers, + }; } - private getPrivateMembers(classDecl: MorphClassDeclaration): Array<{ + private getPrivateMembers(classDecl: ts.ClassDeclaration): Array<{ name: string; isPrivate: boolean; - isSuperPrivate: boolean; }> { - const members: Array<{ - name: string; - isPrivate: boolean; - isSuperPrivate: boolean; - }> = []; - - classDecl.getConstructors().forEach((constructor) => { - constructor.getParameters().forEach((param) => { - if (param.hasModifier('private')) { - members.push({ - name: param.getName(), - isPrivate: true, - isSuperPrivate: false, - }); - } - }); - }); - - const privateMethods = classDecl - .getMethods() - .filter((m) => m.hasModifier('private')); - const privateProperties = classDecl - .getProperties() - .filter((p) => p.hasModifier('private')); - const privateGetters = classDecl - .getGetAccessors() - .filter((a) => a.hasModifier('private')); - const privateSetters = classDecl - .getSetAccessors() - .filter((a) => a.hasModifier('private')); - - [ - ...privateMethods, - ...privateProperties, - ...privateGetters, - ...privateSetters, - ].forEach((member) => { - members.push({ - name: member.getName(), - isPrivate: true, - isSuperPrivate: false, - }); - }); - - [ - ...classDecl.getMethods(), - ...classDecl.getProperties(), - ...classDecl.getGetAccessors(), - ...classDecl.getSetAccessors(), - ].forEach((member) => { - const name = member.getName(); - if (name.startsWith('#')) { - members.push({ - name, - isPrivate: true, - isSuperPrivate: true, - }); - } - }); - - return members; + const ctorParams = this.collectPrivateCtorParams(classDecl); + const classMembers = this.collectPrivateClassMembers(classDecl); + return [...ctorParams, ...classMembers]; } - private getUsedMemberNames(classDecl: MorphClassDeclaration): Set { - const usedMembers = new Set(); - - classDecl.forEachDescendant((node) => { - if (MorphNode.isPropertyAccessExpression(node)) { - const expression = node.getExpression(); - if (MorphNode.isThisExpression(expression)) { - usedMembers.add(node.getName()); + private collectPrivateCtorParams(classDecl: ts.ClassDeclaration): Array<{ + name: string; + isPrivate: boolean; + }> { + const results: Array<{ name: string; isPrivate: boolean }> = []; + for (const member of classDecl.members) { + if (!ts.isConstructorDeclaration(member)) continue; + for (const param of member.parameters) { + if (this.hasPrivateModifier(param)) { + const paramName = this.toName(param.name); + if (paramName) { + results.push({ name: paramName, isPrivate: true }); + } } } + } + return results; + } - if (MorphNode.isElementAccessExpression(node)) { - const expression = node.getExpression(); - const argument = node.getArgumentExpression(); - if ( - MorphNode.isThisExpression(expression) && - MorphNode.isStringLiteral(argument) - ) { - usedMembers.add(argument.getLiteralValue()); - } + private collectPrivateClassMembers(classDecl: ts.ClassDeclaration): Array<{ + name: string; + isPrivate: boolean; + }> { + const results: Array<{ name: string; isPrivate: boolean }> = []; + for (const member of classDecl.members) { + if (ts.isConstructorDeclaration(member)) continue; + const nameNode = ts.getNameOfDeclaration(member); + if (nameNode && ts.isPrivateIdentifier(nameNode)) { + results.push({ name: nameNode.text, isPrivate: true }); + continue; } - - if (MorphNode.isCallExpression(node)) { - const expression = node.getExpression(); - if (MorphNode.isIdentifier(expression)) { - usedMembers.add(expression.getText()); + if (this.hasPrivateModifier(member)) { + const nameStr = this.toName(nameNode); + if (nameStr) { + results.push({ name: nameStr, isPrivate: true }); } } - }); + } + return results; + } + private hasPrivateModifier(node: ts.Declaration): boolean { + const flags = ts.getCombinedModifierFlags(node); + return (flags & ts.ModifierFlags.Private) !== 0; + } + + private toName(name: ts.DeclarationName | undefined): string | undefined { + if (!name) return undefined; + if (ts.isIdentifier(name)) return name.text; + if (ts.isPrivateIdentifier(name)) return name.text; + if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text; + if (ts.isComputedPropertyName(name)) { + const expr = name.expression; + if (ts.isStringLiteral(expr) || ts.isNumericLiteral(expr)) + return expr.text; + return undefined; + } + return undefined; + } + + private getUsedMemberNames(classDecl: ts.ClassDeclaration): Set { + const usedMembers = new Set(); + const className = classDecl.name?.text; + + const addIfThisPropertyAccess = (node: ts.Node): void => { + if (!ts.isPropertyAccessExpression(node)) return; + if (node.expression.kind !== ts.SyntaxKind.ThisKeyword) return; + const name = node.name as ts.Identifier | ts.PrivateIdentifier; + if (ts.isIdentifier(name)) usedMembers.add(name.text); + else if (ts.isPrivateIdentifier(name)) usedMembers.add(name.text); + }; + + const addIfThisElementAccess = (node: ts.Node): void => { + if (!ts.isElementAccessExpression(node)) return; + if (node.expression.kind !== ts.SyntaxKind.ThisKeyword) return; + const arg = node.argumentExpression; + if (arg && ts.isStringLiteral(arg)) usedMembers.add(arg.text); + }; + + const addIfClassPropertyAccess = (node: ts.Node): void => { + if (!ts.isPropertyAccessExpression(node)) return; + if (!className) return; + let expr: ts.Expression = node.expression; + // unwrap parentheses and type assertions/casts + while ( + ts.isParenthesizedExpression(expr) || + ts.isAsExpression(expr) || + ts.isTypeAssertionExpression(expr) + ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expr = (expr as any).expression as ts.Expression; + } + if (!ts.isIdentifier(expr) || expr.text !== className) return; + const name = node.name as ts.Identifier | ts.PrivateIdentifier; + if (ts.isIdentifier(name)) usedMembers.add(name.text); + else if (ts.isPrivateIdentifier(name)) usedMembers.add(name.text); + }; + + const addIfClassElementAccess = (node: ts.Node): void => { + if (!ts.isElementAccessExpression(node)) return; + if (!className) return; + let expr: ts.Expression = node.expression; + while ( + ts.isParenthesizedExpression(expr) || + ts.isAsExpression(expr) || + ts.isTypeAssertionExpression(expr) + ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expr = (expr as any).expression as ts.Expression; + } + if (!ts.isIdentifier(expr) || expr.text !== className) return; + const arg = node.argumentExpression; + if (arg && ts.isStringLiteral(arg)) usedMembers.add(arg.text); + }; + + const visit = (node: ts.Node): void => { + addIfThisPropertyAccess(node); + addIfThisElementAccess(node); + addIfClassPropertyAccess(node); + addIfClassElementAccess(node); + ts.forEachChild(node, visit); + }; + + ts.forEachChild(classDecl, visit); return usedMembers; } } diff --git a/apps/backend/src/services/trend-analysis/x-ray/x-ray-metrics.types.ts b/apps/backend/src/services/trend-analysis/x-ray/x-ray-metrics.types.ts index 4dbcffa..0be38da 100644 --- a/apps/backend/src/services/trend-analysis/x-ray/x-ray-metrics.types.ts +++ b/apps/backend/src/services/trend-analysis/x-ray/x-ray-metrics.types.ts @@ -27,6 +27,7 @@ export interface ClassMetrics { isGodClass: boolean; onlyGettersSetters: boolean; unusedMembers: number; + unusedMemberNames?: string[]; duplicateBlocks: number; duplicateDetails?: Array<{ location: string; diff --git a/apps/frontend/src/app/features/hotspot-city/README.md b/apps/frontend/src/app/features/hotspot-city/README.md new file mode 100644 index 0000000..d364e3d --- /dev/null +++ b/apps/frontend/src/app/features/hotspot-city/README.md @@ -0,0 +1,108 @@ +# Hotspot City 3D + +This document explains how building height, footprint, count changes, and coloring work in the 3D Hotspot City. + +## Modes and Data Flow + +The city renders a list of items prepared by `HotspotCityComponent` and drawn by `City3DComponent`. + +- File mode ("By File"): each building represents a single file. +- Module mode: each building represents an aggregated module bucket. + +Filters (limits, tolerance slider/min score, and global UI filters) modify the underlying data before it is mapped to buildings. + +## Building Height + +Height is normalized from `item.height` in `City3DComponent` using the same function in both modes: + +- Normalization: `height = clamp(1, 20, (value / 100) * 20)` + - Scales the raw value to a maximum of 20 units, minimum of 1 unit. + +Per mode, the raw `value` is: + +### File mode + +- Source: file McCabe complexity (`mcCabe`). +- Mapping in parent: `item.height = file.mcCabe`. +- Rendering: `normalizeComplexity(item.height)` applies the scaling above. +- Notes: Results are combined across metrics and scopes so each file has one row: + + 1. For each configured scope, load hotspots twice with the current filters: once with metric `Length` and once with metric `McCabe`. + 2. Merge all results by `fileName`: + - `loc` = max of `complexity` from `Length` results across all scopes + - `mcCabe` = max of `complexity` from `McCabe` results across all scopes + - `commits`, `changedLines`, `score` = max across all occurrences of that file (both metrics, all scopes) + 3. The city then renders one building per file: footprint from loc (Length), height from mcCabe (McCabe). + +### Module mode + +- Source: aggregated file count for the module (after filters). +- Mapping in parent: `item.height = aggregated.count` (aka `total`). +- Rendering: `normalizeComplexity(item.height)` applies the same scaling. + +## Building Footprint (Width & Depth) + +Footprint is derived from `item.footprint` and determines base size: + +- Renderer normalization: `side = min(6, 0.5 + sqrt(max(1, footprint)) / 6)`. +- Mode-specific footprint sources: + - File mode: file LOC (lines of code). + - Module mode: aggregated file count. + +Larger files/modules appear with a larger base up to a cap, keeping the scene readable. + +## Why the number of buildings changes with filters + +The number of buildings reflects how many items remain after the current filters are applied upstream of rendering. Changes that affect the count include: + +- Tolerance slider (minimum score): items below the min score are excluded by the services/stores. +- Limits (e.g., date ranges, authors, paths): constrain the underlying git log and metrics. +- Selected metric and global filter changes: trigger new loads and aggregations. + +Both file and module datasets are reloaded and recomputed when these inputs change, which can add/remove buildings or change their sizes. + +## Coloring Rules + +Color is assigned in `City3DComponent` and depends on the active mode. + +### File mode (McCabe-based) + +Based on raw McCabe complexity of the file (the same value used for height): + +- `< 10` → Green `#4CAF50` +- `< 20` → Amber `#FFC107` +- `< 40` → Orange `#FF9800` +- `≥ 40` → Red `#F44336` + +### Module mode (bucket presence) + +Based on the presence of hotspot or warning files in the aggregated module: + +- If `countHotspot > 0` → Red `#F44336` +- Else if `countWarning > 0` → Amber `#FFC107` +- Else → Green `#4CAF50` + +The boundaries (`warningBoundary`, `hotspotBoundary`, `maxScore`) come with the aggregated result and reflect the current filtered dataset. They define severity buckets across the app; for coloring here, presence in those buckets drives the color. + +## Interaction (context) + +- Hover: shows a tooltip with file/module details. +- Click: + - File → opens X-Ray dialog for the file. + - Module → opens Hotspot Details dialog filtered to that module and severity range. + +## Summary + +### Per file mode + +- Height: normalized from McCabe complexity. +- Footprint: normalized from LOC. +- Count changes: driven by filters (limits, min score, global filters). +- Colors: thresholded by McCabe (<10 green, <20 amber, <40 orange, ≥40 red). + +### Per module mode + +- Height: normalized from aggregated file count (post-filter total). +- Footprint: normalized from aggregated file count. +- Count changes: driven by filters and module aggregation. +- Colors: by presence of hotspot/warning (red if any hotspot, amber if any warning, else green) using current boundaries. diff --git a/apps/frontend/src/app/features/hotspot-city/city3d.component.ts b/apps/frontend/src/app/features/hotspot-city/city3d.component.ts index a2862ba..e52852a 100644 --- a/apps/frontend/src/app/features/hotspot-city/city3d.component.ts +++ b/apps/frontend/src/app/features/hotspot-city/city3d.component.ts @@ -74,6 +74,7 @@ export class City3DComponent implements OnChanges, OnDestroy { private buildings: THREE.Mesh[] = []; private hoveredObject: THREE.Mesh | null = null; private skipNextClick = false; + private platform?: THREE.Mesh; // simple camera controls state private isLeftMouseDown = false; @@ -93,7 +94,10 @@ export class City3DComponent implements OnChanges, OnDestroy { this.setupScene(); this.startAnimation(); } - this.buildCity(); + const modeChanged = + !!changes['mode'] && + changes['mode'].previousValue !== changes['mode'].currentValue; + this.buildCity(modeChanged || !changes['items']); } } @@ -202,7 +206,7 @@ export class City3DComponent implements OnChanges, OnDestroy { camera.lookAt(this.targetPanX, 0, this.targetPanZ); } - private buildCity(): void { + private buildCity(fitCamera: boolean): void { const scene = this.scene; if (!scene) return; @@ -210,7 +214,16 @@ export class City3DComponent implements OnChanges, OnDestroy { this.buildings.forEach((b) => scene.remove(b)); this.buildings = []; - // platform + if (this.platform) { + scene.remove(this.platform); + const geom = this.platform.geometry as THREE.BufferGeometry | undefined; + const mat = this.platform.material as THREE.Material | THREE.Material[]; + if (geom) geom.dispose(); + if (Array.isArray(mat)) mat.forEach((m) => m.dispose()); + else if (mat) mat.dispose(); + this.platform = undefined; + } + const total = this.items.length; const gridSize = Math.ceil(Math.sqrt(total)); const maxSide = this.items.length @@ -219,24 +232,6 @@ export class City3DComponent implements OnChanges, OnDestroy { ) : 1; const cellSize = Math.max(4, Math.ceil(maxSide + 2)); - const platformSize = Math.max(20, gridSize * cellSize + 6); - - const platformGeometry = new THREE.BoxGeometry( - platformSize, - 0.5, - platformSize - ); - const platformMaterial = new THREE.MeshPhongMaterial({ - color: 0xffffff, - emissive: 0x000000, - emissiveIntensity: 0.0, - shininess: 10, - specular: 0xdddddd, - }); - const platform = new THREE.Mesh(platformGeometry, platformMaterial); - platform.position.set(0, 0, 0); - platform.receiveShadow = false; - scene.add(platform); const bbox = new THREE.Box3(); this.items.forEach((item, index) => { @@ -275,14 +270,44 @@ export class City3DComponent implements OnChanges, OnDestroy { }); const size = bbox.getSize(new THREE.Vector3()); - const maxDim = Math.max(size.x, size.y, size.z); - const camera = this.camera; - if (camera) { - const fov = (camera.fov * Math.PI) / 180; - const fitHeightDistance = maxDim / 2 / Math.tan(fov / 2); - const fitWidthDistance = fitHeightDistance / camera.aspect; - const distance = Math.max(fitHeightDistance, fitWidthDistance) * 1.15; - this.distance = Math.max(20, Math.min(4000, distance)); + const center = bbox.getCenter(new THREE.Vector3()); + + const margin = Math.max(4, cellSize); + const cols = Math.min(total, gridSize); + const rows = Math.ceil(total / gridSize); + const platformWidth = Math.max(8, cols * cellSize + margin); + const platformDepth = Math.max(8, rows * cellSize + margin); + const platformGeometry = new THREE.BoxGeometry( + platformWidth, + 0.5, + platformDepth + ); + const platformMaterial = new THREE.MeshPhongMaterial({ + color: 0xffffff, + emissive: 0x000000, + emissiveIntensity: 0.0, + shininess: 10, + specular: 0xdddddd, + }); + const platform = new THREE.Mesh(platformGeometry, platformMaterial); + platform.position.set(center.x, 0, center.z); + platform.receiveShadow = false; + scene.add(platform); + this.platform = platform; + + if (fitCamera) { + const maxDim = Math.max(size.x, size.y, size.z); + const camera = this.camera; + if (camera) { + const fov = (camera.fov * Math.PI) / 180; + const fitHeightDistance = maxDim / 2 / Math.tan(fov / 2); + const fitWidthDistance = fitHeightDistance / camera.aspect; + const distance = Math.max(fitHeightDistance, fitWidthDistance) * 1.15; + this.distance = Math.max(20, Math.min(4000, distance)); + + this.targetPanX = center.x; + this.targetPanZ = center.z; + } } } diff --git a/apps/frontend/src/app/features/hotspot-city/hotspot-city.component.ts b/apps/frontend/src/app/features/hotspot-city/hotspot-city.component.ts index a339940..72d3eda 100644 --- a/apps/frontend/src/app/features/hotspot-city/hotspot-city.component.ts +++ b/apps/frontend/src/app/features/hotspot-city/hotspot-city.component.ts @@ -105,27 +105,33 @@ export class HotspotCityComponent { score: f.score, }, })); + mapped.sort((a, b) => a.label.localeCompare(b.label)); this.items.set(mapped); this.boundaries.set(undefined); } else { const { aggregated, warningBoundary, hotspotBoundary, maxScore } = this.hotspotStore.aggregatedResult(); - const mapped: City3DItem[] = aggregated.map((a) => ({ - id: this.getModuleKey(a.parent, a.module), - label: this.getModuleKey(a.parent, a.module), - footprint: a.count, - height: a.count, - meta: { - kind: 'module', - moduleKey: this.getModuleKey(a.parent, a.module), - parent: a.parent, - module: a.module, - countHotspot: a.countHotspot, - countWarning: a.countWarning, - countOk: a.countOk, - total: a.count, - }, - })); + const mapped: City3DItem[] = aggregated.map((a) => { + const total = a.countOk + a.countWarning + a.countHotspot; + const moduleKey = this.getModuleKey(a.parent, a.module); + return { + id: moduleKey, + label: moduleKey, + footprint: total, + height: total, + meta: { + kind: 'module', + moduleKey, + parent: a.parent, + module: a.module, + countHotspot: a.countHotspot, + countWarning: a.countWarning, + countOk: a.countOk, + total, + }, + }; + }); + mapped.sort((a, b) => a.label.localeCompare(b.label)); this.items.set(mapped); this.boundaries.set({ warningBoundary, hotspotBoundary, maxScore }); } diff --git a/package-lock.json b/package-lock.json index b5c12ed..a7eb0b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,6 @@ "qtip2": "^3.0.3", "rxjs": "~7.8.0", "three": "0.180.0", - "ts-morph": "^26.0.0", "tslib": "^2.3.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5", @@ -117,20 +116,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -4153,27 +4138,6 @@ "node": ">=18" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -11354,48 +11318,6 @@ "node": ">=10.13.0" } }, - "node_modules/@ts-morph/common": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", - "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.3", - "minimatch": "^10.0.1", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@ts-morph/common/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -14215,17 +14137,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -14683,12 +14594,6 @@ "node": ">= 0.12.0" } }, - "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", - "license": "MIT" - }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -16734,14 +16639,6 @@ "node": ">= 4.0.0" } }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -16781,14 +16678,6 @@ "node": ">=8" } }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -24678,19 +24567,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -25499,17 +25375,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -26558,84 +26423,6 @@ "postcss": "^8.0.0" } }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/postcss-loader": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", @@ -26842,33 +26629,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", @@ -29633,88 +29393,6 @@ "dev": true, "license": "ISC" }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -29801,45 +29479,6 @@ "url": "https://github.com/sponsors/dcastil" } }, - "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/tailwindcss-animate": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", @@ -29850,77 +29489,6 @@ "tailwindcss": ">=3.0.0 || insiders" } }, - "node_modules/tailwindcss/node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tailwindcss/node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/tailwindcss/node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -30236,31 +29804,6 @@ "dev": true, "license": "MIT" }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -30447,14 +29990,6 @@ "typescript": ">=4.2.0" } }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, "node_modules/ts-jest": { "version": "29.2.5", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", @@ -30601,16 +30136,6 @@ "node": ">=8" } }, - "node_modules/ts-morph": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", - "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.27.0", - "code-block-writer": "^13.0.3" - } - }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -30938,6 +30463,7 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 1ee6350..d056546 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "qtip2": "^3.0.3", "rxjs": "~7.8.0", "three": "0.180.0", - "ts-morph": "^26.0.0", "tslib": "^2.3.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5",