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


Quelle  check-architecture-smells.mjs   Sprache: unbekannt

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

#!/usr/bin/env node

import path from "node:path";
import { fileURLToPath } from "node:url";
import ts from "typescript";
import { BUNDLED_PLUGIN_PATH_PREFIX } from "./lib/bundled-plugin-paths.mjs";
import {
  collectTypeScriptInventory,
  normalizeRepoPath,
  resolveRepoSpecifier,
  visitModuleSpecifiers,
  writeLine,
} from "./lib/guard-inventory-utils.mjs";
import {
  collectTypeScriptFilesFromRoots,
  resolveSourceRoots,
  runAsScript,
  toLine,
} from "./lib/ts-guard-utils.mjs";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const scanRoots = resolveSourceRoots(repoRoot, ["src/plugin-sdk", "src/plugins/runtime"]);

function compareEntries(left, right) {
  return (
    left.category.localeCompare(right.category) ||
    left.file.localeCompare(right.file) ||
    left.line - right.line ||
    left.kind.localeCompare(right.kind) ||
    left.specifier.localeCompare(right.specifier) ||
    left.reason.localeCompare(right.reason)
  );
}

function pushEntry(entries, entry) {
  entries.push(entry);
}

function scanPluginSdkExtensionFacadeSmells(sourceFile, filePath) {
  const relativeFile = normalizeRepoPath(repoRoot, filePath);
  if (!relativeFile.startsWith("src/plugin-sdk/")) {
    return [];
  }

  const entries = [];

  visitModuleSpecifiers(ts, sourceFile, ({ kind, specifier, specifierNode }) => {
    if (kind !== "export") {
      return;
    }
    const resolvedPath = resolveRepoSpecifier(repoRoot, specifier, filePath);
    if (!resolvedPath?.startsWith(BUNDLED_PLUGIN_PATH_PREFIX)) {
      return;
    }
    pushEntry(entries, {
      category: "plugin-sdk-extension-facade",
      file: relativeFile,
      line: toLine(sourceFile, specifierNode),
      kind,
      specifier,
      resolvedPath,
      reason: "plugin-sdk public surface re-exports extension-owned implementation",
    });
  });
  return entries;
}

function scanRuntimeTypeImplementationSmells(sourceFile, filePath) {
  const relativeFile = normalizeRepoPath(repoRoot, filePath);
  if (!/^src\/plugins\/runtime\/types(?:-[^/]+)?\.ts$/.test(relativeFile)) {
    return [];
  }

  const entries = [];

  function visit(node) {
    if (
      ts.isImportTypeNode(node) &&
      ts.isLiteralTypeNode(node.argument) &&
      ts.isStringLiteral(node.argument.literal)
    ) {
      const specifier = node.argument.literal.text;
      const resolvedPath = resolveRepoSpecifier(repoRoot, specifier, filePath);
      if (
        resolvedPath &&
        (/^src\/plugins\/runtime\/runtime-[^/]+\.ts$/.test(resolvedPath) ||
          /^extensions\/[^/]+\/runtime-api\.[^/]+$/.test(resolvedPath))
      ) {
        pushEntry(entries, {
          category: "runtime-type-implementation-edge",
          file: relativeFile,
          line: toLine(sourceFile, node.argument.literal),
          kind: "import-type",
          specifier,
          resolvedPath,
          reason: "runtime type file references implementation shim directly",
        });
      }
    }

    ts.forEachChild(node, visit);
  }

  visit(sourceFile);
  return entries;
}

function scanRuntimeServiceLocatorSmells(sourceFile, filePath) {
  const relativeFile = normalizeRepoPath(repoRoot, filePath);
  if (
    !relativeFile.startsWith("src/plugin-sdk/") &&
    !relativeFile.startsWith("src/plugins/runtime/")
  ) {
    return [];
  }

  const entries = [];
  const exportedNames = new Set();
  const runtimeStoreCalls = [];
  const mutableStateNodes = [];

  for (const statement of sourceFile.statements) {
    if (ts.isFunctionDeclaration(statement) && statement.name) {
      const isExported = statement.modifiers?.some(
        (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
      );
      if (isExported) {
        exportedNames.add(statement.name.text);
      }
    } else if (ts.isVariableStatement(statement)) {
      const isExported = statement.modifiers?.some(
        (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
      );
      for (const declaration of statement.declarationList.declarations) {
        if (ts.isIdentifier(declaration.name) && isExported) {
          exportedNames.add(declaration.name.text);
        }
        if (
          !isExported &&
          (statement.declarationList.flags & ts.NodeFlags.Let) !== 0 &&
          ts.isIdentifier(declaration.name)
        ) {
          mutableStateNodes.push(declaration.name);
        }
      }
    }
  }

  function visit(node) {
    if (
      ts.isCallExpression(node) &&
      ts.isIdentifier(node.expression) &&
      node.expression.text === "createPluginRuntimeStore"
    ) {
      runtimeStoreCalls.push(node.expression);
    }

    ts.forEachChild(node, visit);
  }

  visit(sourceFile);

  const getterNames = [...exportedNames].filter((name) => /^get[A-Z]/.test(name));
  const setterNames = [...exportedNames].filter((name) => /^set[A-Z]/.test(name));

  if (runtimeStoreCalls.length > 0 && getterNames.length > 0 && setterNames.length > 0) {
    for (const callNode of runtimeStoreCalls) {
      pushEntry(entries, {
        category: "runtime-service-locator",
        file: relativeFile,
        line: toLine(sourceFile, callNode),
        kind: "runtime-store",
        specifier: "createPluginRuntimeStore",
        resolvedPath: relativeFile,
        reason: `exports paired runtime accessors (${getterNames.join(", ")} / ${setterNames.join(", ")}) over module-global store state`,
      });
    }
  }

  if (mutableStateNodes.length > 0 && getterNames.length > 0 && setterNames.length > 0) {
    for (const identifier of mutableStateNodes) {
      pushEntry(entries, {
        category: "runtime-service-locator",
        file: relativeFile,
        line: toLine(sourceFile, identifier),
        kind: "mutable-state",
        specifier: identifier.text,
        resolvedPath: relativeFile,
        reason: `module-global mutable state backs exported runtime accessors (${getterNames.join(", ")} / ${setterNames.join(", ")})`,
      });
    }
  }

  return entries;
}

export async function collectArchitectureSmells() {
  const files = (await collectTypeScriptFilesFromRoots(scanRoots)).toSorted((left, right) =>
    normalizeRepoPath(repoRoot, left).localeCompare(normalizeRepoPath(repoRoot, right)),
  );
  return await collectTypeScriptInventory({
    ts,
    files,
    compareEntries,
    collectEntries(sourceFile, filePath) {
      return [
        ...scanPluginSdkExtensionFacadeSmells(sourceFile, filePath),
        ...scanRuntimeTypeImplementationSmells(sourceFile, filePath),
        ...scanRuntimeServiceLocatorSmells(sourceFile, filePath),
      ];
    },
  });
}

function formatInventoryHuman(inventory) {
  if (inventory.length === 0) {
    return "No architecture smells found for the configured checks.";
  }

  const lines = ["Architecture smell inventory:"];
  let activeCategory = "";
  let activeFile = "";
  for (const entry of inventory) {
    if (entry.category !== activeCategory) {
      activeCategory = entry.category;
      activeFile = "";
      lines.push(entry.category);
    }
    if (entry.file !== activeFile) {
      activeFile = entry.file;
      lines.push(`  ${activeFile}`);
    }
    lines.push(`    - line ${entry.line} [${entry.kind}] ${entry.reason}`);
    lines.push(`      specifier: ${entry.specifier}`);
    lines.push(`      resolved: ${entry.resolvedPath}`);
  }
  return lines.join("\n");
}

export async function runArchitectureSmellsCheck(argv = process.argv.slice(2), io) {
  const streams = io ?? { stdout: process.stdout, stderr: process.stderr };
  const json = argv.includes("--json");
  const inventory = await collectArchitectureSmells();

  if (json) {
    writeLine(streams.stdout, JSON.stringify(inventory, null, 2));
    return 0;
  }

  writeLine(streams.stdout, formatInventoryHuman(inventory));
  writeLine(streams.stdout, `${inventory.length} smell${inventory.length === 1 ? "" : "s"} found.`);
  return 0;
}

export async function main(argv = process.argv.slice(2), io) {
  return await runArchitectureSmellsCheck(argv, io);
}

runAsScript(import.meta.url, main);

[Dauer der Verarbeitung: 0.33 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


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