ESLint InterlaceESLint Interlace
Plugin: conventionsRules

prefer-dependency-version-strategy

ESLint rule documentation for prefer-dependency-version-strategy

📡 Live from GitHub — This documentation is fetched directly from prefer-dependency-version-strategy.md and cached for 6 hours.

Keywords: dependency version, package.json, semantic versioning, caret, tilde, exact version, version strategy, ESLint rule, monorepo, dependency management, npm, npm, yarn, auto-fix, LLM-optimized, code quality

Enforce consistent version strategy (caret ^, tilde ~, exact, range, or any) for package.json dependencies. This rule complements @nx/dependency-checks by ensur

Enforce consistent version strategy (caret ^, tilde ~, exact, range, or any) for package.json dependencies. This rule complements @nx/dependency-checks by ensuring version specifier format consistency across your monorepo.

Quick Summary

AspectDetails
SeverityWarning (best practice)
Auto-Fix✅ Yes (automatically adds/removes version prefixes)
CategoryDevelopment
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForMonorepos, teams standardizing dependency management
StrategiesCaret (^), Tilde (~), Exact, Range, Any

Rule Details

Why This Matters

IssueImpactSolution
🔄 ConsistencyMixed version strategies cause confusionStandardize on one strategy
🔒 SecurityExact versions miss security patchesUse caret for flexibility
MaintenanceHard to update dependencies systematicallyConsistent strategy enables automation
📦 MonorepoDifferent packages use different strategiesUnified approach across packages

Configuration

OptionTypeDefaultDescription
strategy'caret' | 'tilde' | 'exact' | 'range' | 'any''caret'Default version strategy to enforce for all packages
allowWorkspacebooleantrueAllow workspace: protocol versions (monorepo support)
allowFilebooleantrueAllow file: protocol versions
allowLinkbooleantrueAllow link: protocol versions
overridesRecord<string, VersionStrategy>{}Package-specific strategy overrides. Key is package name, value is strategy

Strategy Options

StrategyPrefixDescriptionExample
caret^Allows minor and patch updates (recommended)^1.0.0
tilde~Allows only patch updates~1.0.0
exactnoneRequires exact version match1.0.0
range>=, <, ||Allows range operators>=1.0.0 <2.0.0
anyanyAllows any version format (disables rule)Any format

Examples

❌ Incorrect

{
  "dependencies": {
    "react": "18.2.0",           // Missing caret
    "lodash": "~4.17.21",        // Using tilde when caret expected
    "typescript": "^5.0.0"       // Correct
  }
}

✅ Correct

{
  "dependencies": {
    "react": "^18.2.0",          // Caret prefix
    "lodash": "^4.17.21",        // Caret prefix
    "typescript": "^5.0.0"       // Caret prefix
  }
}

Monorepo Support

{
  "dependencies": {
    "@my-org/utils": "workspace:*",     // ✅ Allowed (workspace protocol)
    "@my-org/shared": "file:../shared", // ✅ Allowed (file protocol)
    "external-lib": "^1.0.0"           // ✅ Enforced strategy
  }
}

Usage

Basic Configuration

// eslint.config.mjs
import llmOptimized from 'eslint-plugin-conventions';

export default [
  {
    files: ['**/package.json'],
    plugins: {
      'eslint-plugin-llm-optimized': llmOptimized,
    },
    rules: {
      'eslint-plugin-llm-optimized/development/prefer-dependency-version-strategy': [
        'warn',
        {
          strategy: 'caret',
          allowWorkspace: true,
          allowFile: true,
          allowLink: true,
        },
      ],
    },
    languageOptions: {
      parser: await import('jsonc-eslint-parser'),
    },
  },
];

Custom Strategy

// Enforce exact versions (strict)
{
  'eslint-plugin-llm-optimized/development/prefer-dependency-version-strategy': [
    'error',
    { strategy: 'exact' }
  ]
}

// Enforce tilde (patch updates only)
{
  'eslint-plugin-llm-optimized/development/prefer-dependency-version-strategy': [
    'warn',
    { strategy: 'tilde' }
  ]
}

Package-Specific Overrides

Override the default strategy for specific packages. This allows you to have strict versioning for critical dependencies while maintaining flexibility for others.

Basic Override Example

{
  'eslint-plugin-llm-optimized/development/prefer-dependency-version-strategy': [
    'warn',
    {
      strategy: 'caret', // Default: use caret for all packages
      overrides: {
        'react': 'exact',        // React must be exact version
        'react-dom': 'exact',    // React DOM must be exact version
        'lodash': 'tilde',       // Lodash uses tilde (patch updates only)
        'typescript': 'exact',   // TypeScript must be exact version
        '@types/node': 'caret',  // Type definitions can use caret
      }
    }
  ]
}

Real-World React Project Example

{
  'eslint-plugin-llm-optimized/development/prefer-dependency-version-strategy': [
    'warn',
    {
      strategy: 'caret',
      overrides: {
        // Core framework - exact versions for stability
        'react': 'exact',
        'react-dom': 'exact',
        'next': 'exact',
        
        // Build tools - exact versions
        'typescript': 'exact',
        'vite': 'exact',
        'esbuild': 'exact',
        
        // Utility libraries - tilde (patch updates only)
        'lodash': 'tilde',
        'date-fns': 'tilde',
        'ramda': 'tilde',
        
        // Type definitions - caret (flexible)
        '@types/react': 'caret',
        '@types/node': 'caret',
        '@types/lodash': 'caret',
      }
    }
  ]
}

Monorepo Example with Workspace Support

{
  'eslint-plugin-llm-optimized/development/prefer-dependency-version-strategy': [
    'warn',
    {
      strategy: 'caret',
      allowWorkspace: true, // Allow workspace: protocol
      overrides: {
        // Shared dependencies - exact versions
        'typescript': 'exact',
        '@types/node': 'exact',
        
        // Framework - exact
        'react': 'exact',
        
        // Utilities - tilde
        'lodash': 'tilde',
      }
    }
  ]
}

Use Cases:

  • Critical dependencies (React, TypeScript, Next.js): Use exact to prevent breaking changes
  • Utility libraries (lodash, date-fns): Use tilde for patch updates only
  • Type definitions (@types/*): Use caret for flexibility
  • Most packages: Use default caret strategy
  • Disable for specific packages: Use 'any' to allow any version format

Integration with @nx/dependency-checks

This rule complements @nx/dependency-checks:

RulePurpose
@nx/dependency-checksValidates version alignment with lockfile
prefer-dependency-version-strategyEnforces version prefix format consistency

Use both together:

{
  rules: {
    '@nx/dependency-checks': ['error', { /* ... */ }],
    'eslint-plugin-llm-optimized/development/prefer-dependency-version-strategy': [
      'warn',
      { strategy: 'caret' }
    ],
  },
}

Auto-Fix Examples

Example 1: With Package-Specific Overrides

Configuration:

{
  strategy: 'caret', // Default for all packages
  overrides: {
    'react': 'exact',        // React must be exact
    'react-dom': 'exact',    // React DOM must be exact
    'lodash': 'tilde',       // Lodash uses tilde
    'typescript': 'exact',   // TypeScript must be exact
  }
}

Before (Incorrect):

{
  "dependencies": {
    "react": "^18.2.0",           // ❌ Should be exact
    "react-dom": "^18.2.0",       // ❌ Should be exact
    "lodash": "^4.17.21",        // ❌ Should be tilde
    "typescript": "^5.0.0",      // ❌ Should be exact
    "express": "4.18.0",         // ❌ Missing caret (default)
    "axios": "1.6.0"             // ❌ Missing caret (default)
  }
}

After (Auto-Fixed):

{
  "dependencies": {
    "react": "18.2.0",           // ✅ Exact (override)
    "react-dom": "18.2.0",       // ✅ Exact (override)
    "lodash": "~4.17.21",        // ✅ Tilde (override)
    "typescript": "5.0.0",       // ✅ Exact (override)
    "express": "^4.18.0",        // ✅ Caret (default)
    "axios": "^1.6.0"            // ✅ Caret (default)
  }
}

Example 2: React Project with Strict Versioning

Configuration:

{
  strategy: 'caret', // Default: flexible for most packages
  overrides: {
    // Core React dependencies - exact versions
    'react': 'exact',
    'react-dom': 'exact',
    'react-router-dom': 'exact',
    
    // Build tools - exact versions
    'typescript': 'exact',
    'vite': 'exact',
    
    // Utility libraries - tilde (patch updates only)
    'lodash': 'tilde',
    'date-fns': 'tilde',
    
    // Type definitions - caret (flexible)
    '@types/react': 'caret',
    '@types/node': 'caret',
  }
}

Before:

{
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.20.0",
    "typescript": "^5.3.0",
    "vite": "^5.0.0",
    "lodash": "^4.17.21",
    "date-fns": "^2.30.0",
    "@types/react": "18.2.0",
    "@types/node": "20.10.0"
  }
}

After (Auto-Fixed):

{
  "dependencies": {
    "react": "18.2.0",              // ✅ Exact
    "react-dom": "18.2.0",          // ✅ Exact
    "react-router-dom": "6.20.0",    // ✅ Exact
    "typescript": "5.3.0",          // ✅ Exact
    "vite": "5.0.0",                // ✅ Exact
    "lodash": "~4.17.21",           // ✅ Tilde
    "date-fns": "~2.30.0",          // ✅ Tilde
    "@types/react": "^18.2.0",      // ✅ Caret (default)
    "@types/node": "^20.10.0"      // ✅ Caret (default)
  }
}

Example 3: Monorepo with Mixed Strategies

Configuration:

{
  strategy: 'caret', // Default
  allowWorkspace: true,
  overrides: {
    // Critical shared dependencies - exact
    'typescript': 'exact',
    '@types/node': 'exact',
    
    // Framework packages - exact
    'react': 'exact',
    'next': 'exact',
    
    // Utility packages - tilde
    'lodash': 'tilde',
    'ramda': 'tilde',
  }
}

Before:

{
  "dependencies": {
    "@my-org/shared": "workspace:*",  // ✅ Allowed (workspace protocol)
    "typescript": "^5.0.0",           // ❌ Should be exact
    "@types/node": "^20.0.0",        // ❌ Should be exact
    "react": "^18.2.0",               // ❌ Should be exact
    "next": "^14.0.0",                // ❌ Should be exact
    "lodash": "^4.17.21",             // ❌ Should be tilde
    "ramda": "^0.29.0",               // ❌ Should be tilde
    "axios": "1.6.0",                 // ❌ Missing caret (default)
    "express": "4.18.0"               // ❌ Missing caret (default)
  }
}

After (Auto-Fixed):

{
  "dependencies": {
    "@my-org/shared": "workspace:*",  // ✅ Allowed (workspace protocol)
    "typescript": "5.0.0",            // ✅ Exact (override)
    "@types/node": "20.0.0",         // ✅ Exact (override)
    "react": "18.2.0",                // ✅ Exact (override)
    "next": "14.0.0",                 // ✅ Exact (override)
    "lodash": "~4.17.21",             // ✅ Tilde (override)
    "ramda": "~0.29.0",               // ✅ Tilde (override)
    "axios": "^1.6.0",                // ✅ Caret (default)
    "express": "^4.18.0"              // ✅ Caret (default)
  }
}

Example 4: Simple Configuration (No Overrides)

Configuration:

{
  strategy: 'caret', // Default for all packages
}

Before:

{
  "dependencies": {
    "typescript": "5.0.0",
    "lodash": "4.17.21",
    "express": "4.18.0"
  }
}

After (Default caret strategy):

{
  "dependencies": {
    "typescript": "^5.0.0",
    "lodash": "^4.17.21",
    "express": "^4.18.0"
  }
}

Example 5: Disable Rule for Specific Packages

Configuration:

{
  strategy: 'caret',
  overrides: {
    'experimental-package': 'any',  // Allow any version format
    'legacy-package': 'any',        // Allow any version format
  }
}

Result:

  • experimental-package and legacy-package can use any version format (exact, caret, tilde, range, etc.)
  • All other packages must use caret (^) prefix

Best Practices

PracticeReason
✅ Use caret (default)Allows security patches and minor updates
✅ Use overrides for critical depsPin React, TypeScript, etc. to exact versions
✅ Allow workspace protocolsEssential for monorepo support
✅ Combine with @nx/dependency-checksComplete dependency validation
⚠️ Avoid exact for all packagesMisses security patches
⚠️ Use exact only for critical depsWhen version pinning is required
💡 Use tilde for utilitiesPatch updates only (lodash, date-fns)
{
  strategy: 'caret', // Default: flexible for most packages
  overrides: {
    // Critical framework dependencies - exact versions
    'react': 'exact',
    'react-dom': 'exact',
    'typescript': 'exact',
    
    // Utility libraries - tilde (patch updates only)
    'lodash': 'tilde',
    'date-fns': 'tilde',
    
    // Type definitions - caret (flexible)
    '@types/node': 'caret',
    '@types/react': 'caret',
  }
}

Resources

Version History

  • 1.0.0 - Initial release with caret, tilde, exact, range, and any strategies

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Dynamic Variable References

Why: Static analysis cannot trace values stored in variables or passed through function parameters.

// ❌ NOT DETECTED - Value from variable
const value = externalSource();
processValue(value); // Variable origin not tracked

Mitigation: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs.

Imported Values

Why: When values come from imports, the rule cannot analyze their origin or construction.

// ❌ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not tracked

Mitigation: Ensure imported values follow the same constraints. Use TypeScript for type safety.

On this page

No Headings