npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@angular-modernizer/plugin-solid

v0.1.3

Published

Analysis plugin for detecting SOLID principle violations.

Readme

@angular-modernizer/plugin-solid

SOLID principle violation detection and architectural analysis for Angular codebases.

Overview

The @angular-modernizer/plugin-solid package provides analysis rules for detecting violations of SOLID principles and other architectural issues. This plugin powers the scan-solid tool in the MCP server.

SOLID Principles Coverage

Single Responsibility Principle (SRP)

Detects classes that have too many responsibilities by examining class sizes, method counts, and coupling metrics. Also detects single methods that mix 4 or more concern categories across 20 or more lines.

Open/Closed Principle (OCP)

Identifies code that requires modification to extend behavior, including switch statements and if-else chains that grow with each new case.

Liskov Substitution Principle (LSP)

Finds inheritance hierarchies where subclasses break substitutability by overriding behavior in incompatible ways.

Interface Segregation Principle (ISP)

Detects fat interfaces that force unnecessary dependencies on implementing classes.

Dependency Inversion Principle (DIP)

Finds direct instantiation and tight coupling violations, including new expressions for services and concrete constructor dependencies.

Analysis Rules

SRPSingleResponsibilityViolationRule

Rule ID: solid:srp-single-responsibility-violation Severity: Warning

Detects classes with more than 15 public methods or mixed responsibility categories across 3 or more distinct concern domains. Also detects single methods that mix 4 or more concern categories across 20 or more lines.


DIPViolationRule

Rule ID: solid:dip-violation-direct-instantiation Severity: High

Detects direct instantiation of services and the service locator anti-pattern.

Detection patterns:

  • new MyService() in constructors or methods
  • new MyRepository() in component methods
  • Direct instantiation of classes ending with "Service", "Repository", "Store"
  • injector.get() service locator calls

Before:

// Direct instantiation
@Component({...})
export class UserComponent {
  private userService = new UserService(); // DIP violation
  private httpClient = new HttpClient();   // DIP violation
}

// Service locator
constructor(private injector: Injector) {
  this.service = injector.get(UserService); // DIP violation
}

After:

@Component({...})
export class UserComponent {
  private userService = inject(UserService);
  private httpClient = inject(HttpClient);
}

Analysis Result:

{
  "ruleId": "solid:dip-violation-direct-instantiation",
  "violations": [
    {
      "id": "dip-violation-1",
      "severity": "high",
      "message": "Direct instantiation of UserService violates Dependency Inversion Principle",
      "location": {
        "filePath": "src/app/user.component.ts",
        "startLine": 5,
        "startColumn": 25
      },
      "category": "solid-violation",
      "autoFix": {
        "type": "inject-conversion",
        "description": "Convert to dependency injection using inject()"
      }
    }
  ]
}

DIPConcreteDependencyRule

Rule ID: solid:dip-violation-concrete-dependency Severity: Warning

Detects constructor parameters that inject concrete @Injectable classes rather than interfaces. Has a transform counterpart (ServiceDipViolationTransformOrchestrator) in this plugin that creates an I{ClassName} interface and InjectionToken in the service file, and updates the consuming file with an @Inject decorator.


GodSwitchAnalysisRule

Rule ID: solid:god-switch Severity: Warning

Detects switch statements with too many cases (default threshold: 10), classified by switch type.

Detection patterns:

  • Switch statements exceeding the configurable threshold
  • Classification by switch type: type-based, status-based, action-based, event-based, command-based, state-based, kind-based, category-based
  • Confidence scoring for violations (0.7 to 1.0)

Before:

// God Switch with 15 cases
export class OrderProcessor {
  processOrder(status: string): void {
    switch (status) {
      case 'pending': break;
      case 'processing': break;
      case 'shipped': break;
      case 'delivered': break;
      case 'cancelled': break;
      case 'refunded': break;
      case 'returned': break;
      case 'failed': break;
      case 'on-hold': break;
      case 'completed': break;
      case 'archived': break;
      case 'disputed': break;
      case 'escalated': break;
      case 'resolved': break;
      case 'closed': break;
      default: break;
    }
  }
}

After (Factory Pattern):

interface OrderStatusStrategy {
  execute(): void;
}

class PendingStrategy implements OrderStatusStrategy {
  execute(): void { /* ... */ }
}

class ProcessingStrategy implements OrderStatusStrategy {
  execute(): void { /* ... */ }
}

// ... more strategy classes

class OrderStatusFactory {
  private strategies = new Map<string, OrderStatusStrategy>([
    ['pending', new PendingStrategy()],
    ['processing', new ProcessingStrategy()],
    // ... more strategies
  ]);

  createStrategy(status: string): OrderStatusStrategy {
    return this.strategies.get(status) ?? new DefaultStrategy();
  }
}

Analysis Result:

{
  "ruleId": "solid:god-switch",
  "message": "God Switch detected: 15 cases (status-based). Consider Factory Pattern.",
  "filePath": "src/app/order-processor.ts",
  "line": 3,
  "column": 4,
  "suggestedFix": "Refactor to Factory Pattern with strategy objects for each case type",
  "metadata": {
    "caseCount": 15,
    "switchType": "status-based",
    "switchExpression": "status",
    "hasDefault": true,
    "confidence": 0.9,
    "caseLabels": ["pending", "processing", "shipped", "..."],
    "violationType": "god-switch",
    "principle": "Open/Closed Principle"
  }
}

Configuration:

interface GodSwitchConfig {
  /**
   * Maximum allowed case clauses before violation
   * Default: 10
   */
  maxCases?: number;
}

// Usage
const context = ContextFactory.createAnalysisContext({
  sourceFile,
  project,
  api: createPublicApi(),
  config: {
    solid: {
      godSwitch: {
        maxCases: 15, // Custom threshold
      },
    },
  },
});

ImportDirectionViolationRule

Rule ID: solid:import-direction-violation Severity: Error

Detects imports that violate the configured layer direction rules. Requires layers configuration in .angular-modernizer.json. Silently skips violations when layer detection returns null for either file to avoid noise from unrecognized paths.


OCPViolationRule

Rule ID: solid:ocp-violation Severity: Warning

Detects classes that require modification to extend behavior, including type-checking patterns (instanceof chains, type switches) and hardcoded branching on type identifiers.


LSPViolationRule

Rule ID: solid:lsp-violation Severity: Warning

Detects subclasses that break Liskov Substitution by overriding methods in ways that violate the base class contract.


GodSwitchTransformRule

Rule ID: solid:god-switch-transform Transformation Type: god-switch-transform

Transforms God Switches into Factory Pattern implementations.

Transformation steps:

  1. Detect switch statements exceeding the threshold
  2. Extract metadata: case logic, parameters, return types, mutations
  3. Generate strategy interface for polymorphic dispatch
  4. Generate one strategy class per case with extracted logic
  5. Generate factory class with strategy map and factory method
  6. Replace switch statement with factory call
  7. Update imports for generated code

Before:

export class PaymentProcessor {
  processPayment(method: string, amount: number): string {
    let result = '';
    switch (method) {
      case 'credit-card':
        result = `Processing credit card payment of ${amount}`;
        break;
      case 'debit-card':
        result = `Processing debit card payment of ${amount}`;
        break;
      case 'paypal':
        result = `Processing PayPal payment of ${amount}`;
        break;
      case 'bank-transfer':
        result = `Processing bank transfer of ${amount}`;
        break;
      case 'crypto':
        result = `Processing crypto payment of ${amount}`;
        break;
      // ... 10+ more cases
      default:
        result = 'Unknown payment method';
        break;
    }
    return result;
  }
}

After:

interface MethodStrategy {
  execute(amount: number): string;
}

class CreditCardStrategy implements MethodStrategy {
  execute(amount: number): string {
    return `Processing credit card payment of ${amount}`;
  }
}

class DebitCardStrategy implements MethodStrategy {
  execute(amount: number): string {
    return `Processing debit card payment of ${amount}`;
  }
}

// ... more strategy classes

class MethodFactory {
  private strategies = new Map<string, MethodStrategy>([
    ['credit-card', new CreditCardStrategy()],
    ['debit-card', new DebitCardStrategy()],
    ['paypal', new PaypalStrategy()],
    // ... more strategies
  ]);

  createStrategy(method: string): MethodStrategy {
    return this.strategies.get(method) ?? new DefaultStrategy();
  }
}

export class PaymentProcessor {
  private factory = new MethodFactory();

  processPayment(method: string, amount: number): string {
    const strategy = this.factory.createStrategy(method);
    return strategy.execute(amount);
  }
}

Configuration:

interface GodSwitchTransformConfig {
  /**
   * Minimum case count to trigger transformation
   * Default: 10
   */
  minCaseCount?: number;

  /**
   * Generate strategy interface
   * Default: true
   */
  generateInterfaces?: boolean;

  /**
   * Generate factory class
   * Default: true
   */
  generateFactory?: boolean;

  /**
   * Strategy class naming pattern
   * Placeholders: {CaseValue}, {SwitchExpression}
   * Default: "{CaseValue}Strategy"
   */
  strategyNamingPattern?: string;

  /**
   * Factory class naming pattern
   * Default: "{SwitchExpression}Factory"
   */
  factoryNamingPattern?: string;
}

// Usage via MCP tool
const result = await transformCode({
  filePath: 'src/app/payment-processor.ts',
  transformationType: 'god-switch-transform',
  config: {
    '@angular-modernizer/plugin-solid': {
      transformationType: 'god-switch-transform',
      godSwitch: {
        minCaseCount: 10,
        generateInterfaces: true,
        generateFactory: true,
        strategyNamingPattern: '{CaseValue}Strategy',
        factoryNamingPattern: '{SwitchExpression}Factory',
      },
    },
  },
});

Transformation guarantees:

  • Idempotent: safe to run multiple times, skips already-transformed switches
  • Atomic: all-or-nothing semantics with automatic rollback on failure
  • Mutation handling: preserves variable mutations across case boundaries
  • Fallthrough support: groups fallthrough cases into a single strategy
  • Type safety: generates TypeScript interfaces and typed factory methods

Factory Pattern Comparison

| Aspect | God Switch | Factory Pattern | |-|-|-| | Extensibility | Modify switch for new cases | Add new strategy class | | Testability | Test entire switch | Test strategies independently | | Maintainability | Large, complex switch | Small, focused classes | | OCP Compliance | Violates OCP | Follows OCP | | Code Reuse | Duplicate logic | Reusable strategies | | Type Safety | String-based cases | Interface-based polymorphism |


Usage Examples

Basic SOLID Analysis

import { Kernel, RealFileSystemAdapter } from '@angular-modernizer/core';
import { SolidPlugin } from '@angular-modernizer/plugin-solid';

const kernel = new Kernel({
  tsConfigPath: './tsconfig.json',
  fileSystem: new RealFileSystemAdapter(),
  plugins: [new SolidPlugin()],
});

await kernel.initialize();

// Get SOLID analysis rules
const plugin = kernel.getPlugin('@angular-modernizer/plugin-solid');
const rules = plugin.getAnalysisRules();
const dipRule = rules.find(
  (r) => r.id === 'solid:dip-violation-direct-instantiation',
);

// Analyze a file
const project = kernel.getProject();
const sourceFile = project.addSourceFileAtPath('src/app/user.component.ts');

const context = ContextFactory.createAnalysisContext({
  sourceFile,
  project,
  api: createPublicApi(),
  config: {},
});

const result = await dipRule.analyze(context);

console.log('DIP Violations found:', result.violations.length);
result.violations.forEach((v) => {
  console.log(`- ${v.message} at line ${v.location.startLine}`);
});

Detection + Transformation (God Switch)

// Get both analysis and transform rules
const analysisRule = plugin
  .getAnalysisRules()
  .find((r) => r.id === 'solid:god-switch');
const transformRule = plugin
  .getTransformRules()
  .find((r) => r.id === 'solid:god-switch-transform');

// 1. Detect violations
const analysisContext = ContextFactory.createAnalysisContext({
  sourceFile,
  project,
  api: createPublicApi(project),
  config: {},
});

const violations = await analysisRule.analyze(analysisContext);

if (violations.length > 0) {
  console.log(`Found ${violations.length} God Switches, transforming...`);

  // 2. Transform violations
  const transformContext = ContextFactory.createTransformContext({
    sourceFile,
    project,
    api: createPublicApi(project),
    config: {
      '@angular-modernizer/plugin-solid': {
        transformationType: 'god-switch-transform',
        godSwitch: {
          minCaseCount: 10,
          generateInterfaces: true,
          generateFactory: true,
        },
      },
    },
  });

  const result = await transformRule.transform(transformContext);

  if (result.modified) {
    await sourceFile.save();
  }
}

MCP Tool Integration

// MCP tool usage (handled by adapter-mcp)
const violations = await callTool("scan-solid", {
  rootPath: "/path/to/project",
  rules: ["solid:dip-violation-direct-instantiation"],
  includeAutoFixes: true
});

Batch Analysis

import { glob } from 'glob';

const files = await glob('src/**/*.{ts,tsx}', {
  ignore: ['**/node_modules/**', '**/dist/**'],
});

const allViolations: Violation[] = [];

for (const filePath of files) {
  const sourceFile = project.addSourceFileAtPath(filePath);
  const context = ContextFactory.createAnalysisContext({
    sourceFile,
    project,
    api: createPublicApi(),
  });

  for (const rule of rules) {
    const result = await rule.analyze(context);
    allViolations.push(...result.violations);
  }
}

// Group by severity
const bySeverity = allViolations.reduce(
  (acc, v) => {
    acc[v.severity] = (acc[v.severity] || 0) + 1;
    return acc;
  },
  {} as Record<string, number>,
);

console.log('SOLID Analysis Summary:');
console.log('High:', bySeverity.high || 0);
console.log('Medium:', bySeverity.medium || 0);
console.log('Low:', bySeverity.low || 0);

Configuration Options

DIP Violation Config

interface DIPViolationConfig {
  /**
   * Service class suffixes to detect
   * Default: ['Service', 'Repository', 'Store', 'Manager']
   */
  serviceSuffixes?: string[];

  /**
   * Exclude patterns for files/directories
   * Default: ['**/node_modules/**', '**/dist/**']
   */
  excludePatterns?: string[];

  /**
   * Include auto-fix suggestions
   * Default: true
   */
  includeAutoFixes?: boolean;

  /**
   * Custom service detection patterns
   */
  customPatterns?: RegExp[];
}

Example Configuration

const context = ContextFactory.createAnalysisContext({
  sourceFile,
  project,
  api: createPublicApi(),
  config: {
    serviceSuffixes: ['Service', 'Repository', 'Store', 'Api'],
    excludePatterns: ['**/generated/**', '**/vendor/**'],
    includeAutoFixes: true,
    customPatterns: [/.*Client$/, /.*Provider$/],
  },
});

Package Structure

packages/plugin-solid/
  src/
    rules/
      dip-violation.rule.ts
      srp-single-responsibility-violation.rule.ts
      god-switch-analysis.rule.ts
      ocp-violation.rule.ts
      lsp-violation.rule.ts
      import-direction-violation.rule.ts
      dip-violation-concrete-dependency.rule.ts
    orchestrators/
      god-switch-transform-orchestrator.ts
      service-dip-violation-transform.orchestrator.ts
    transform-rules/
      god-switch-transform.rule.ts
    solid-plugin.ts
    index.ts
    types.ts
  __tests__/
    rules/
      dip-violation.rule.test.ts
      god-switch-analysis.rule.test.ts
      (and more)
    orchestrators/
      god-switch-transform-orchestrator.test.ts
      god-switch-mutation-preservation.test.ts
    transform-rules/
      god-switch-transform.rule.test.ts
    solid-plugin.test.ts
  package.json
  tsconfig.json
  jest.config.js
  README.md

Testing

# Run SOLID plugin tests
pnpm test --filter @angular-modernizer/plugin-solid

# Run with coverage
pnpm test --filter @angular-modernizer/plugin-solid --coverage

Test Examples

describe('DIPViolationRule', () => {
  let rule: DIPViolationRule;

  beforeEach(() => {
    rule = new DIPViolationRule();
  });

  it('should detect direct service instantiation', async () => {
    const sourceFile = project.createSourceFile(
      'test.component.ts',
      `
      @Component({})
      export class TestComponent {
        private service = new UserService(); // Violation
        private repo = new UserRepository(); // Violation
      }
    `,
    );

    const context = ContextFactory.createAnalysisContext({
      sourceFile,
      project,
      api: createPublicApi(),
    });

    const result = await rule.analyze(context);

    expect(result.violations).toHaveLength(2);
    expect(result.violations[0].message).toContain('UserService');
    expect(result.violations[1].message).toContain('UserRepository');
  });

  it('should not flag dependency injection', async () => {
    const sourceFile = project.createSourceFile(
      'test.component.ts',
      `
      @Component({})
      export class TestComponent {
        private service = inject(UserService); // OK
        private repo = inject(UserRepository); // OK
      }
    `,
    );

    const context = ContextFactory.createAnalysisContext({
      sourceFile,
      project,
      api: createPublicApi(),
    });

    const result = await rule.analyze(context);

    expect(result.violations).toHaveLength(0);
  });
});

Dependencies

  • @angular-modernizer/api - Public API with analysis tools
  • @angular-modernizer/core - Kernel and infrastructure
  • @angular-modernizer/plugin-system - Plugin contracts and contexts

SOLID Principle Reference

Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Common violations:

  • Direct instantiation of services (new MyService())
  • Missing interfaces for services
  • Tight coupling between high-level and low-level modules

Best practices:

  • Use dependency injection
  • Program to interfaces, not implementations
  • Create abstractions for volatile dependencies

Single Responsibility Principle (SRP)

A class should have only one reason to change. One responsibility per class.

Open/Closed Principle (OCP)

Open for extension, closed for modification. Use polymorphism instead of conditionals.

Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types. Inheritance should preserve behavior.

Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they don't use. Prefer small, focused interfaces.


Contributing

When adding new SOLID analysis rules:

  1. Focus on one principle - each rule should address one SOLID principle
  2. Use PublicApi tools - leverage existing analysis infrastructure
  3. Include auto-fixes when possible
  4. Write comprehensive tests covering various violation scenarios
  5. Document with before/after examples

Adding a New SOLID Rule

// 1. Implement the rule
class MySolidRule implements AnalysisRule {
  readonly id = 'solid:my-solid-rule';
  readonly name = 'My SOLID Rule';
  readonly description = 'Detects violations of a specific SOLID principle';

  async analyze(context: AnalysisContext): Promise<AnalysisResult> {
    const { api, sourceFile } = context;
    const violations: Violation[] = [];

    // Use PublicApi for analysis
    // Detect violations and create Violation objects

    return {
      ruleId: this.id,
      violations,
      metadata: {
        totalViolations: violations.length,
      },
    };
  }
}

// 2. Add to plugin
class SolidPlugin implements Plugin {
  getAnalysisRules(): AnalysisRule[] {
    return [
      new DIPViolationRule(),
      new MySolidRule(), // Add here
    ];
  }
}

See Also