Angular Modernizer
    Preparing search index...

    Module @angular-modernizer/plugin-architecture - v1.2.0

    Architecture and code quality analysis plugin for the Angular Modernizer.

    ArchitecturePlugin is the entry point for the kernel and registers 17 analysis rules: import cycles (CircularDependencyRule), layer rules (ImportDirectionViolationRule, LayerBoundaryViolationRule, ContentPlacementRule), size and responsibility rules (GodObjectRule, ComponentComplexityRule, ServiceArchitectureRule, ServiceBagAnalysisRule, StaticInStatefulClassRule), library extraction (LibraryExtractionAnalysisRule) and seven rules that are only available through the plugin.

    Also exported for reuse by other plugins and tools: the value-import graph (ImportGraph), module resolution (ModuleResolver), layer classification (LayerPathMatcher, loadLayersFromSource), the library extraction scanner (LibraryExtractionScanner, LibraryCategorizationEngine) and helpers for responsibility patterns and RxJS subscription safety.

    @angular-modernizer/plugin-architecture

    Architecture and code quality analysis plugin for the Angular Modernization Platform.

    The plugin provides 17 analysis rules for structural problems in Angular code bases: import cycles, imports across layer boundaries, files in the wrong layer, oversized classes and components, services that mix concerns, and groups of shared code that could become a library. All rules only report; the plugin has no transformation or validation rules. Fixes are done by transform rules in other plugins (for example the service bag split in @angular-modernizer/plugin-angular).

    ArchitecturePlugin is the entry point for the kernel. Besides the rules, the package exports building blocks that other plugins and tools reuse: the value-import graph, the module resolver, the layer path matcher, the library extraction scanner and helpers for responsibility patterns and RxJS subscription safety.

    npm install @angular-modernizer/plugin-architecture
    

    The package is an ES module and needs Node.js ^20.19.0 || ^22.12.0 || >=24.0.0. It depends on @angular-modernizer/plugin-system, @angular-modernizer/api, @angular-modernizer/core and ts-morph.

    Rule ID Severity Description
    solid:circular-dependency error Runtime import cycles (aliases, relative paths, barrels) between project files; cycles with DI or decorator usage are flagged as NG0919 risks
    solid:import-direction-violation error Imports against built-in layer conventions (app, features, shared, core, libs, ...) inferred from paths and tsconfig aliases
    architecture:layer-boundary-violation error Imports that violate the layer topology configured in layers or layersSource
    architecture:content-placement warning Files outside the layers the placement map allows for their content type
    solid:god-object-violation error Classes with more than 30 methods, more than 1000 lines, more than 10 dependencies or mixed architectural concerns
    architecture:component-complexity warning Components with more than 10 methods, more than 2 concerns or without OnPush
    architecture:service-architecture warning Service files that export interfaces, types or enums, and services with more than 5 constructor dependencies
    architecture:service-bag error Services and static helpers with 11 or more public methods that mix concerns or backend areas
    architecture:static-in-stateful-class warning Public static methods in undecorated classes with instance state
    architecture:library-extraction-opportunity warning Cohesive artifact groups in shared folders that could be extracted into Angular libraries
    architecture:model-separation warning Files that export models, interfaces, enums, tokens, constants, functions or classes next to implementation code
    architecture:rxjs-memory-leak warning Undisposed subscriptions and missing takeUntil patterns
    architecture:advanced-content-placement warning Folder structure and domain-driven design patterns
    architecture:error-handling-pattern warning Empty or log-only catch blocks, silent catchError, thrown non-Error values, unhandled async errors
    architecture:inheritance-analysis warning Deep inheritance chains, god object base classes, composition over inheritance
    architecture:performance-pattern warning Performance anti-patterns: complex template getters and expressions, missing trackBy, OnPush or virtual scrolling
    architecture:cross-file-architecture warning Imports against the layer direction: domain, application or infrastructure code importing presentation code

    The first ten rule classes are exported (CircularDependencyRule, ImportDirectionViolationRule, LayerBoundaryViolationRule, ContentPlacementRule, GodObjectRule, ComponentComplexityRule, ServiceArchitectureRule, ServiceBagAnalysisRule, StaticInStatefulClassRule, LibraryExtractionAnalysisRule). The last seven are only available through ArchitecturePlugin.getAnalysisRules().

    Three rule IDs use the solid: prefix; the rules belong to this plugin.

    ArchitecturePlugin.initialize() reads .angular-modernizer.json from the project root of the kernel (the directory of tsConfigPath, otherwise the current working directory). If the file cannot be loaded, the error is logged and all rules run with their defaults and without layers.

    architecture:layer-boundary-violation and architecture:content-placement need a layer topology. Without it both rules report nothing.

    {
    "layers": [
    {
    "name": "core",
    "alias": "@core",
    "paths": ["src/app/core/**"],
    "canImportFrom": []
    },
    {
    "name": "shared",
    "alias": "@shared",
    "paths": ["src/app/shared/**"],
    "canImportFrom": ["@core"]
    },
    {
    "name": "features",
    "alias": "@features",
    "paths": ["src/app/features/**"],
    "canImportFrom": ["@core", "@shared"]
    }
    ]
    }
    • name: layer name used in messages and in canImportFrom
    • alias: tsconfig path alias of the layer
    • paths: globs relative to the project root. A layer without paths uses the directory its alias maps to in tsconfig paths. The most specific matching pattern wins, independent of the order of the layers.
    • canImportFrom: names or aliases of the layers this layer may import from. An empty array forbids imports from all other layers.
    • level (optional): position in the hierarchy, lower = more foundational. When both layers of an import have a level, imports into the same or a lower level are allowed and canImportFrom only lists the allowed imports into higher levels.

    The layer-boundary rule checks every import and export ... from. Specifiers are resolved with TypeScript module resolution (tsconfig paths, baseUrl, relative paths, index.ts); unresolved aliased imports fall back to alias-prefix matching. Type-only imports are reported too and marked with typeOnly: true in the metadata.

    Imports can be exempted with a project comment marker, none is built in: with "rules": { "architecture:layer-boundary-violation": { "suppressionComments": ["arch-ignore"] } } an import is skipped when a comment on its line or on the line directly above starts with arch-ignore.

    A project that already defines its layers for an arch-validator script can reference that module instead of repeating it:

    {
    "layersSource": {
    "format": "arch-validator",
    "path": "tools/arch-validator/config.js",
    "exportName": "CONFIG"
    }
    }

    Each layer key becomes name, paths becomes [key + '/**'], pathAlias becomes alias, allowedImports becomes canImportFrom and level is kept, so the validator's level semantics apply. The validator's own suppression comment is not taken over; set it with suppressionComments. When both layers and layersSource are set, layersSource wins and the differences are logged and returned by ArchitecturePlugin.getLayerDriftWarnings().

    architecture:content-placement also needs a placement map: the layers (names or aliases) allowed per content type. Content types: service, component, guard, interceptor, directive, pipe, model, utility.

    {
    "rules": {
    "architecture:content-placement": {
    "placement": {
    "service": ["@core", "@shared"],
    "guard": ["@core"],
    "component": ["@shared", "@features"]
    }
    }
    }
    }

    Barrel files (index.ts, public-api.ts, exports.ts), specs, tests, config files and declaration files are skipped.

    architecture:library-extraction-opportunity reads these fields from rules['architecture:library-extraction-opportunity']:

    Field Default Meaning
    minCohesionScore 0.3 Minimum cohesion (0-1) of an artifact cluster
    minReusabilityScore 20 Minimum reusability score (0-100) of an artifact
    targetFolders ["shared", "common", "core", "libs"] Folder names to scan; a path segment matches when it contains a name
    categoryOverrides {} Fixed category per class name (ui, forms, layout, utils, data, core, feature)
    excludePatterns [] Globs of files to skip; spec and test files are always skipped

    The other fields of the config type (libraryType, outputFolder, libraryPrefix, ...) are used by the library extraction transform, not by this rule.

    The scan covers the whole project. The rule runs it once per project and file count and reports each opportunity once, at its primary file.

    The remaining rules have fixed thresholds and need no configuration. solid:circular-dependency resolves imports with the tsconfig the project was loaded from (else the nearest tsconfig.json) and reads files from disk, so the result does not depend on which files a scan has loaded.

    The scan-architecture MCP tool runs this plugin; scan-all runs it together with the other analysis plugins.

    {
    "rootPath": "/path/to/angular/project",
    "rules": ["solid:circular-dependency", "architecture:layer-boundary-violation"]
    }

    Without rules, all 17 rules run.

    import path from 'node:path';
    import { Kernel } from '@angular-modernizer/core';
    import { createPublicApi } from '@angular-modernizer/api';
    import { ContextFactory } from '@angular-modernizer/plugin-system';
    import { ArchitecturePlugin } from '@angular-modernizer/plugin-architecture';

    const kernel = new Kernel({
    tsConfigPath: path.join(projectPath, 'tsconfig.json'),
    plugins: [new ArchitecturePlugin()],
    });
    await kernel.initialize();

    const project = kernel.getProject();
    const api = createPublicApi(project);
    const rules = kernel
    .getPlugin('@angular-modernizer/plugin-architecture')!
    .getAnalysisRules();

    for (const sourceFile of project.getSourceFiles()) {
    const context = ContextFactory.createAnalysisContext({
    sourceFile,
    project,
    api,
    });
    for (const rule of rules) {
    const results = await rule.analyze(context);
    results.forEach((r) => console.info(`${r.ruleId} ${r.filePath}:${r.line}: ${r.message}`));
    }
    }
    import { LayerBoundaryViolationRule } from '@angular-modernizer/plugin-architecture';

    const rule = new LayerBoundaryViolationRule(layers, { projectRoot });
    const results = await rule.analyze(context);
    pnpm --filter @angular-modernizer/plugin-architecture build
    pnpm --filter @angular-modernizer/plugin-architecture test

    Enumerations

    LibraryCategory

    Classes

    ArchitecturePlugin
    CircularDependencyRule
    ComponentComplexityRule
    ContentPlacementRule
    GodObjectRule
    ImportDirectionViolationRule
    ImportGraph
    LayerBoundaryViolationRule
    LayerPathMatcher
    LibraryCategorizationEngine
    LibraryExtractionAnalysisRule
    LibraryExtractionScanner
    ModuleResolver
    ServiceArchitectureRule
    ServiceBagAnalysisRule
    StaticInStatefulClassRule

    Interfaces

    ArtifactMetrics
    CategorizationResult
    CircularDependencyDiEdge
    CircularDependencyMetadata
    CircularDependencyRuleOptions
    ContentPlacementMetadata
    ContentPlacementOptions
    CyclePath
    EffortEstimate
    GraphFileSource
    ImportEdge
    LayerBoundaryOptions
    LayerBoundaryViolationMetadata
    LibraryExtractionOpportunity
    LibraryExtractionRuleConfig
    LibraryExtractionScannerConfig
    ParsedImport
    ResponsibilitySignal
    StaticInStatefulMetadata

    Type Aliases

    CandidateKind
    CategorizationConfidence
    CategoryOverrides
    ImportKind
    MigrationComplexity
    ModuleImporter

    Variables

    RESPONSIBILITY_PATTERNS

    Functions

    analyzeMethodAst
    analyzeMethodResponsibility
    clearImportGraphParseCache
    diffLayerConfigs
    findSubscribeCalls
    hasDestroyRefCleanup
    isSelfCompletingSubscribe
    isSubscribeCall
    loadLayersFromSource
    mapArchValidatorConfig
    parseImports