or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

component-rules.mdcomputed-property-rules.mdember-utils.mdindex.mdlegacy-configuration.mdmigration-rules.mdmodern-configuration.mdplugin-configuration.mdroute-rules.mdservice-rules.mdtest-rules.md
tile.json

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();
  }
}