Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  analyze.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import path from "node:path";
import ts from "typescript";
import {
  canonicalSymbolInfo,
  countIdentifierUsages,
  countNamespacePropertyUsages,
  createProgramContext,
  getRepoRevision,
} from "./context.js";
import type {
  ProgramContext,
  PublicEntrypoint,
  RankedCandidates,
  ReferenceEvent,
  TopologyEnvelope,
  TopologyRecord,
  TopologyReportName,
  TopologyScope,
} from "./types.js";

function pushUnique(values: string[], next: string | null | undefined) {
  if (!next) {
    return;
  }
  if (!values.includes(next)) {
    values.push(next);
  }
}

function sortUnique(values: string[]) {
  values.sort((left, right) => left.localeCompare(right));
}

function clampScore(value: number): number {
  return Math.max(0, Math.min(100, Math.round(value)));
}

function isTypeOnlyCandidate(record: Pick<TopologyRecord, "kind">): boolean {
  return record.kind === "interface" || record.kind === "type";
}

function computeSharednessScore(record: TopologyRecord): number {
  const extensionWeight = record.productionExtensions.length * 30;
  const packageWeight = record.productionPackages.length * 20;
  const internalWeight = record.internalRefCount > 0 ? 10 : 0;
  const publicSpecifierWeight = Math.min(record.publicSpecifiers.length, 4) * 5;
  const typeWeight = record.isTypeOnlyCandidate ? 10 : 0;
  const testOnlyPenalty = record.productionRefCount === 0 && record.testRefCount > 0 ? 25 : 0;
  return clampScore(
    extensionWeight +
      packageWeight +
      internalWeight +
      publicSpecifierWeight +
      typeWeight -
      testOnlyPenalty,
  );
}

function computeMoveBackToOwnerScore(record: TopologyRecord): number {
  const singleExtensionWeight = record.productionExtensions.length === 1 ? 45 : 0;
  const noNonExtensionOwnersWeight = record.productionPackages.length === 0 ? 20 : 0;
  const runtimeWeight = record.isTypeOnlyCandidate ? 0 : 10;
  const usedWeight = record.productionRefCount > 0 ? 10 : 0;
  const publicSpecifierWeight = record.publicSpecifiers.length > 1 ? 5 : 0;
  const multiOwnerPenalty = record.productionOwners.length > 1 ? 35 : 0;
  const packagePenalty = record.productionPackages.length > 0 ? 25 : 0;
  return clampScore(
    singleExtensionWeight +
      noNonExtensionOwnersWeight +
      runtimeWeight +
      usedWeight +
      publicSpecifierWeight -
      multiOwnerPenalty -
      packagePenalty,
  );
}

function createRecord(info: ReturnType<typeof canonicalSymbolInfo>): TopologyRecord {
  return {
    canonicalKey: info.canonicalKey,
    declarationPath: info.declarationPath,
    declarationLine: info.declarationLine,
    kind: info.kind,
    aliasName: info.aliasName,
    entrypoints: [],
    exportNames: [],
    publicSpecifiers: [],
    internalRefCount: 0,
    productionRefCount: 0,
    testRefCount: 0,
    internalImportCount: 0,
    productionImportCount: 0,
    testImportCount: 0,
    internalConsumers: [],
    productionConsumers: [],
    testConsumers: [],
    productionExtensions: [],
    productionPackages: [],
    productionOwners: [],
    isTypeOnlyCandidate: info.kind === "interface" || info.kind === "type",
    sharednessScore: 0,
    moveBackToOwnerScore: 0,
  };
}

function bucketConsumer(record: TopologyRecord, event: ReferenceEvent) {
  if (event.bucket === "internal") {
    record.internalImportCount += event.importCount;
    record.internalRefCount += event.usageCount;
    pushUnique(record.internalConsumers, event.consumerPath);
    return;
  }
  if (event.bucket === "test") {
    record.testImportCount += event.importCount;
    record.testRefCount += event.usageCount;
    pushUnique(record.testConsumers, event.consumerPath);
    return;
  }
  record.productionImportCount += event.importCount;
  record.productionRefCount += event.usageCount;
  pushUnique(record.productionConsumers, event.consumerPath);
  pushUnique(record.productionExtensions, event.extensionId);
  pushUnique(record.productionPackages, event.packageOwner);
  pushUnique(record.productionOwners, event.owner);
}

function addEntrypointMetadata(
  record: TopologyRecord,
  entrypoint: PublicEntrypoint,
  exportName: string,
  aliasName?: string,
) {
  pushUnique(record.entrypoints, entrypoint.entrypoint);
  pushUnique(record.exportNames, exportName);
  pushUnique(record.publicSpecifiers, entrypoint.importSpecifier);
  if (aliasName) {
    pushUnique(record.exportNames, aliasName);
  }
}

function buildScopeMaps(context: ProgramContext, scope: TopologyScope) {
  const recordByCanonicalKey = new Map<string, TopologyRecord>();
  const recordBySpecifierAndExportName = new Map<string, Map<string, TopologyRecord>>();

  for (const entrypoint of scope.entrypoints) {
    const absolutePath = path.join(context.repoRoot, entrypoint.sourcePath);
    const sourceFile = context.program.getSourceFile(absolutePath);
    if (!sourceFile) {
      continue;
    }
    const moduleSymbol = context.checker.getSymbolAtLocation(sourceFile);
    if (!moduleSymbol) {
      continue;
    }
    const exportMap = new Map<string, TopologyRecord>();
    for (const exportedSymbol of context.checker.getExportsOfModule(moduleSymbol)) {
      const info = canonicalSymbolInfo(context, exportedSymbol);
      let record = recordByCanonicalKey.get(info.canonicalKey);
      if (!record) {
        record = createRecord(info);
        recordByCanonicalKey.set(info.canonicalKey, record);
      }
      addEntrypointMetadata(record, entrypoint, exportedSymbol.getName(), info.aliasName);
      exportMap.set(exportedSymbol.getName(), record);
    }
    recordBySpecifierAndExportName.set(entrypoint.importSpecifier, exportMap);
  }

  return { recordByCanonicalKey, recordBySpecifierAndExportName };
}

function collectReferenceEvents(
  context: ProgramContext,
  scope: TopologyScope,
  recordBySpecifierAndExportName: Map<string, Map<string, TopologyRecord>>,
  includeTests: boolean,
): ReferenceEvent[] {
  const events: ReferenceEvent[] = [];
  for (const sourceFile of context.program.getSourceFiles()) {
    if (sourceFile.isDeclarationFile) {
      continue;
    }
    const normalizedFileName = context.normalizePath(sourceFile.fileName);
    if (!normalizedFileName.startsWith(context.normalizePath(context.repoRoot))) {
      continue;
    }
    const relPath = context.relativeToRepo(sourceFile.fileName);
    const bucket = scope.classifyUsageBucket(relPath);
    if (!includeTests && bucket === "test") {
      continue;
    }

    for (const statement of sourceFile.statements) {
      if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
        continue;
      }
      const importSpecifier = statement.moduleSpecifier.text.trim();
      if (!scope.importFilter(importSpecifier)) {
        continue;
      }
      const recordMap = recordBySpecifierAndExportName.get(importSpecifier);
      if (!recordMap) {
        continue;
      }
      const clause = statement.importClause;
      if (!clause?.namedBindings) {
        continue;
      }

      if (ts.isNamedImports(clause.namedBindings)) {
        for (const element of clause.namedBindings.elements) {
          const importedName = element.propertyName?.text ?? element.name.text;
          const record = recordMap.get(importedName);
          if (!record) {
            continue;
          }
          const localSymbol = context.checker.getSymbolAtLocation(element.name);
          if (!localSymbol) {
            continue;
          }
          events.push({
            canonicalKey: record.canonicalKey,
            bucket,
            consumerPath: relPath,
            usageCount: countIdentifierUsages(context, sourceFile, localSymbol, element.name.text),
            importCount: 1,
            importSpecifier,
            owner: bucket === "production" ? scope.ownerForPath(relPath) : null,
            extensionId: bucket === "production" ? scope.extensionForPath(relPath) : null,
            packageOwner: bucket === "production" ? scope.packageOwnerForPath(relPath) : null,
          });
        }
        continue;
      }

      if (ts.isNamespaceImport(clause.namedBindings)) {
        const namespaceSymbol = context.checker.getSymbolAtLocation(clause.namedBindings.name);
        if (!namespaceSymbol) {
          continue;
        }
        for (const [exportedName, record] of recordMap.entries()) {
          const usageCount = countNamespacePropertyUsages(
            context,
            sourceFile,
            namespaceSymbol,
            exportedName,
          );
          if (usageCount <= 0) {
            continue;
          }
          events.push({
            canonicalKey: record.canonicalKey,
            bucket,
            consumerPath: relPath,
            usageCount,
            importCount: 1,
            importSpecifier,
            owner: bucket === "production" ? scope.ownerForPath(relPath) : null,
            extensionId: bucket === "production" ? scope.extensionForPath(relPath) : null,
            packageOwner: bucket === "production" ? scope.packageOwnerForPath(relPath) : null,
          });
        }
      }
    }
  }
  return events;
}

function finalizeRecords(records: TopologyRecord[]) {
  for (const record of records) {
    sortUnique(record.entrypoints);
    sortUnique(record.exportNames);
    sortUnique(record.publicSpecifiers);
    sortUnique(record.internalConsumers);
    sortUnique(record.productionConsumers);
    sortUnique(record.testConsumers);
    sortUnique(record.productionExtensions);
    sortUnique(record.productionPackages);
    sortUnique(record.productionOwners);
    record.isTypeOnlyCandidate = isTypeOnlyCandidate(record);
    record.sharednessScore = computeSharednessScore(record);
    record.moveBackToOwnerScore = computeMoveBackToOwnerScore(record);
  }
  return records.toSorted((left, right) => {
    const byRefs =
      right.productionRefCount +
      right.testRefCount +
      right.internalRefCount -
      (left.productionRefCount + left.testRefCount + left.internalRefCount);
    if (byRefs !== 0) {
      return byRefs;
    }
    return (
      left.publicSpecifiers[0].localeCompare(right.publicSpecifiers[0]) ||
      left.exportNames[0].localeCompare(right.exportNames[0])
    );
  });
}

function buildRankedCandidates(records: TopologyRecord[], limit: number): RankedCandidates {
  return {
    candidateToMove: records
      .filter(
        (record) =>
          record.productionOwners.length === 1 &&
          record.productionExtensions.length === 1 &&
          record.productionRefCount > 0,
      )
      .toSorted((left, right) => right.moveBackToOwnerScore - left.moveBackToOwnerScore)
      .slice(0, limit),
    duplicatedPublicExports: records
      .filter((record) => record.publicSpecifiers.length > 1)
      .toSorted((left, right) => right.publicSpecifiers.length - left.publicSpecifiers.length)
      .slice(0, limit),
    singleOwnerShared: records
      .filter((record) => record.productionOwners.length === 1 && record.productionImportCount > 0)
      .toSorted((left, right) => right.productionRefCount - left.productionRefCount)
      .slice(0, limit),
  };
}

export function analyzeTopology(options: {
  repoRoot: string;
  scope: TopologyScope;
  report: TopologyReportName;
  includeTests?: boolean;
  limit?: number;
  tsconfigName?: string;
}): TopologyEnvelope {
  const includeTests = options.includeTests ?? true;
  const limit = options.limit ?? 25;
  const context = createProgramContext(options.repoRoot, options.tsconfigName);
  const { recordByCanonicalKey, recordBySpecifierAndExportName } = buildScopeMaps(
    context,
    options.scope,
  );
  const events = collectReferenceEvents(
    context,
    options.scope,
    recordBySpecifierAndExportName,
    includeTests,
  );
  for (const event of events) {
    const record = recordByCanonicalKey.get(event.canonicalKey);
    if (record) {
      bucketConsumer(record, event);
    }
  }
  const allRecords = finalizeRecords([...recordByCanonicalKey.values()]);
  const filteredRecords = filterRecordsForReport(allRecords, options.report);

  return {
    metadata: {
      tool: "ts-topology",
      version: 1,
      generatedAt: new Date().toISOString(),
      repoRevision: getRepoRevision(options.repoRoot),
      tsconfigPath: context.tsconfigPath,
    },
    scope: {
      id: options.scope.id,
      description: options.scope.description,
      repoRoot: options.repoRoot,
      entrypoints: options.scope.entrypoints,
      includeTests,
    },
    report: options.report,
    totals: {
      exports: allRecords.length,
      usedByProduction: allRecords.filter((record) => record.productionImportCount > 0).length,
      usedByTests: allRecords.filter((record) => record.testImportCount > 0).length,
      usedInternally: allRecords.filter((record) => record.internalImportCount > 0).length,
      singleOwnerShared: allRecords.filter(
        (record) => record.productionOwners.length === 1 && record.productionImportCount > 0,
      ).length,
      unused: allRecords.filter(
        (record) =>
          record.productionImportCount === 0 &&
          record.testImportCount === 0 &&
          record.internalImportCount === 0,
      ).length,
    },
    rankedCandidates: buildRankedCandidates(allRecords, limit),
    records: filteredRecords,
  };
}

export function filterRecordsForReport(
  records: TopologyRecord[],
  report: TopologyReportName,
): TopologyRecord[] {
  switch (report) {
    case "owner-map":
      return records.filter((record) => record.productionImportCount > 0);
    case "single-owner-shared":
      return records.filter(
        (record) => record.productionOwners.length === 1 && record.productionImportCount > 0,
      );
    case "unused-public-surface":
      return records.filter(
        (record) =>
          record.productionImportCount === 0 &&
          record.testImportCount === 0 &&
          record.internalImportCount === 0,
      );
    case "consumer-topology":
      return records.filter(
        (record) =>
          record.productionImportCount > 0 ||
          record.testImportCount > 0 ||
          record.internalImportCount > 0,
      );
    case "public-surface-usage":
      return records;
  }
  throw new Error("Unsupported topology report");
}

¤ Dauer der Verarbeitung: 0.30 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge