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