CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-eslint-plugin-ember

ESLint plugin for Ember.js apps providing 97 specialized linting rules based on commonly known good practices in the Ember ecosystem

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

service-rules.mddocs/

Service Rules

Service-related linting rules for dependency injection, service usage patterns, and optimization. These rules help maintain clean service architecture and prevent common service-related anti-patterns.

Capabilities

Service Injection Rules

Rules governing service injection patterns and dependency management.

/**
 * Require explicit service injections
 * Prevents implicit service injections
 */
'ember/no-implicit-injections': ESLintRule;

/**
 * Require explicit service injection arguments
 * Ensures service names are explicitly specified
 */
'ember/no-implicit-service-injection-argument': ESLintRule;

/**
 * Remove unnecessary service injection arguments
 * Cleans up redundant service name specifications
 */
'ember/no-unnecessary-service-injection-argument': ESLintRule;

Usage Examples:

// ❌ Bad - implicit service injection (triggers no-implicit-injections)
export default Component.extend({
  // Implicit injection based on property name
  store: service()
});

// ✅ Good - explicit service injection
export default Component.extend({
  store: service('store')
});

// ❌ Bad - implicit service name (triggers no-implicit-service-injection-argument)
export default class MyComponent extends Component {
  @service() currentUser; // Service name inferred from property name
}

// ✅ Good - explicit service name
export default class MyComponent extends Component {
  @service('current-user') currentUser;
}

// ❌ Bad - unnecessary service argument (triggers no-unnecessary-service-injection-argument)
export default class MyComponent extends Component {
  @service('store') store; // 'store' is redundant when property name matches
}

// ✅ Good - omit redundant service name
export default class MyComponent extends Component {
  @service store;
}

Service Usage Optimization

Rules for optimizing service usage and preventing waste.

/**
 * Remove unused service injections
 * Identifies and removes services that are injected but not used
 */
'ember/no-unused-services': ESLintRule;

/**
 * Restrict specific service injections
 * Prevents injection of restricted or deprecated services
 */
'ember/no-restricted-service-injections': ESLintRule;

Usage Examples:

// ❌ Bad - unused service injection (triggers no-unused-services)
export default class MyComponent extends Component {
  @service store;
  @service currentUser; // Injected but never used
  
  @action
  loadData() {
    return this.store.findAll('user');
  }
}

// ✅ Good - only inject used services
export default class MyComponent extends Component {
  @service store;
  
  @action
  loadData() {
    return this.store.findAll('user');
  }
}

// ❌ Bad - restricted service injection (triggers no-restricted-service-injections)
// Configuration in eslint.config.js:
// 'ember/no-restricted-service-injections': ['error', ['legacy-api', 'deprecated-service']]

export default class MyComponent extends Component {
  @service legacyApi; // Restricted service
}

// ✅ Good - use allowed services
export default class MyComponent extends Component {
  @service modernApi;
}

Service Definition Rules

Rules for service class definition and structure.

/**
 * Check if node is an Ember Service
 * Used internally for detecting service definitions
 */
function isEmberService(context: ESLintContext, node: ASTNode): boolean;

/**
 * Service naming conventions
 * Services should follow kebab-case naming
 */
function convertServiceNameToKebabCase(serviceName: string): string;

Usage Examples:

// Service definition patterns
export default class CurrentUserService extends Service {
  @tracked user = null;
  
  async loadUser() {
    this.user = await this.store.findRecord('user', 'current');
  }
  
  get isAuthenticated() {
    return !!this.user;
  }
}

// Service naming - prefer kebab-case
// ❌ Bad - camelCase service name
@service currentUser;

// ✅ Good - kebab-case service name  
@service('current-user') currentUser;

// ✅ Good - when property name matches kebab-case
@service('current-user') currentUser; // explicit
@service currentUser; // when service is registered as 'current-user'

Service Architecture Rules

Rules promoting good service architecture and preventing anti-patterns.

/**
 * Service injection best practices
 * Guidelines for proper service usage in different contexts
 */
interface ServiceBestPractices {
  /** Services should be stateless when possible */
  preferStatelessServices: boolean;
  /** Services should have single responsibilities */
  singleResponsibility: boolean;
  /** Services should avoid direct DOM manipulation */
  avoidDOMAccess: boolean;
  /** Services should use dependency injection */
  useDependencyInjection: boolean;
}

Usage Examples:

// ✅ Good - well-structured service
export default class ApiService extends Service {
  @service session;
  
  async request(url, options = {}) {
    const headers = {
      'Content-Type': 'application/json',
      ...options.headers
    };
    
    if (this.session.isAuthenticated) {
      headers.Authorization = `Bearer ${this.session.token}`;
    }
    
    return fetch(url, { ...options, headers });
  }
}

// ❌ Bad - service with too many responsibilities
export default class UtilityService extends Service {
  // Too many unrelated methods
  formatDate(date) { /* ... */ }
  makeApiCall(url) { /* ... */ }
  validateForm(form) { /* ... */ }
  manipulateDOM(element) { /* ... */ } // Should not be in service
}

// ✅ Good - focused services
export default class DateFormatterService extends Service {
  formatDate(date, format = 'MM/DD/YYYY') {
    // Single responsibility - date formatting
    return moment(date).format(format);
  }
}

export default class FormValidatorService extends Service {  
  validateForm(form, rules) {
    // Single responsibility - form validation
    return this.validateFields(form, rules);
  }
}

Service Lifecycle Rules

Rules for service lifecycle management and initialization.

/**
 * Service initialization patterns
 * Best practices for service setup and teardown
 */
interface ServiceLifecycle {
  /** Use init() for service initialization */
  init(): void;
  /** Use willDestroy() for cleanup */
  willDestroy(): void;
  /** Avoid constructor in Ember services */
  avoidConstructor: boolean;
}

Usage Examples:

// ✅ Good - proper service lifecycle
export default class WebSocketService extends Service {
  connection = null;
  
  init() {
    super.init(...arguments);
    this.connect();
  }
  
  willDestroy() {
    super.willDestroy(...arguments);
    this.disconnect();
  }
  
  connect() {
    this.connection = new WebSocket('ws://localhost:8080');
  }
  
  disconnect() {
    if (this.connection) {
      this.connection.close();
      this.connection = null;
    }
  }
}

// ❌ Bad - using constructor instead of init
export default class BadService extends Service {
  constructor() {
    super(...arguments);
    // Service initialization should be in init()
    this.setup();
  }
}

// ✅ Good - use init for initialization
export default class GoodService extends Service {
  init() {
    super.init(...arguments);
    this.setup();
  }
}

docs

component-rules.md

computed-property-rules.md

ember-utils.md

index.md

legacy-configuration.md

migration-rules.md

modern-configuration.md

plugin-configuration.md

route-rules.md

service-rules.md

test-rules.md

tile.json