ESLint plugin for Ember.js apps providing 97 specialized linting rules based on commonly known good practices in the Ember ecosystem
npx @tessl/cli install tessl/npm-eslint-plugin-ember@12.7.0ESLint Plugin Ember is a comprehensive ESLint plugin for Ember.js applications that provides 97 specialized linting rules based on commonly known good practices in the Ember ecosystem. It supports both legacy ESLint configuration (.eslintrc.js) and modern flat config (eslint.config.js) formats, with recommended rule sets for standard Ember apps as well as specialized configurations for TypeScript (gts) and template (gjs) files.
npm install --save-dev eslint-plugin-ember// .eslintrc.js
module.exports = {
plugins: ['ember'],
extends: [
'eslint:recommended',
'plugin:ember/recommended'
],
rules: {
// override / enable optional rules
'ember/no-replace-test-comments': 'error'
}
};// eslint.config.js
const eslintPluginEmberRecommended = require('eslint-plugin-ember/configs/recommended');
module.exports = [
...eslintPluginEmberRecommended,
];import eslintPluginEmber from 'eslint-plugin-ember/recommended';
export default [
eslintPluginEmber.base,
eslintPluginEmber.gjs, // for .gjs files
eslintPluginEmber.gts, // for .gts files
];// eslint.config.js (flat config)
const eslintPluginEmberRecommended = require('eslint-plugin-ember/configs/recommended');
module.exports = [
...eslintPluginEmberRecommended,
{
// Override specific rules
rules: {
'ember/no-replace-test-comments': 'error',
'ember/no-jquery': 'warn'
}
}
];For .gjs/.gts files with template syntax:
// eslint.config.js
import eslintPluginEmber from 'eslint-plugin-ember/recommended';
export default [
eslintPluginEmber.base, // For .js/.ts files
eslintPluginEmber.gjs, // For .gjs files
eslintPluginEmber.gts, // For .gts files
];ESLint Plugin Ember is built around several key components:
Core plugin structure and configuration management for ESLint integration. Essential for setting up the plugin in any ESLint configuration.
interface ESLintPlugin {
meta: {
name: string;
version: string;
};
rules: Record<string, ESLintRule>;
configs: Record<string, ESLintConfig>;
utils: {
ember: EmberUtils;
};
processors: {
noop: ESLintProcessor;
};
}Pre-configured rule sets optimized for modern ESLint flat config format, supporting different file types and Ember patterns.
interface ModernConfig {
plugin: ESLintPlugin;
parser: ESLintParser;
base: FlatConfig;
gjs: FlatConfig;
gts: FlatConfig;
configs: {
base: FlatConfig;
gjs: FlatConfig;
gts: FlatConfig;
};
}
interface FlatConfig {
name: string;
files: string[];
plugins?: Record<string, ESLintPlugin>;
languageOptions?: LanguageOptions;
processor?: string;
rules: Record<string, ESLintRuleConfig>;
}Traditional ESLint configuration presets for backward compatibility with existing ESLint setups.
type LegacyConfig = Array<{
rules: Record<string, ESLintRuleConfig>;
}>;Linting rules specific to Ember components, enforcing modern component patterns and preventing common anti-patterns.
interface ComponentRules {
'ember/no-actions-hash': ESLintRule;
'ember/no-attrs-in-components': ESLintRule;
'ember/no-classic-components': ESLintRule;
'ember/no-component-lifecycle-hooks': ESLintRule;
'ember/order-in-components': ESLintRule;
// ... additional component rules
}Rules for Ember routes covering naming conventions, lifecycle management, and best practices.
interface RouteRules {
'ember/no-capital-letters-in-routes': ESLintRule;
'ember/no-controller-access-in-routes': ESLintRule;
'ember/route-path-style': ESLintRule;
'ember/routes-segments-snake-case': ESLintRule;
'ember/order-in-routes': ESLintRule;
// ... additional route rules
}Service-related linting rules for dependency injection, service usage patterns, and optimization.
interface ServiceRules {
'ember/no-implicit-injections': ESLintRule;
'ember/no-unnecessary-service-injection-argument': ESLintRule;
'ember/no-unused-services': ESLintRule;
'ember/no-restricted-service-injections': ESLintRule;
// ... additional service rules
}Rules governing computed properties, dependencies, and modern reactive patterns.
interface ComputedPropertyRules {
'ember/no-arrow-function-computed-properties': ESLintRule;
'ember/no-computed-properties-in-native-classes': ESLintRule;
'ember/no-duplicate-dependent-keys': ESLintRule;
'ember/require-computed-property-dependencies': ESLintRule;
'ember/use-brace-expansion': ESLintRule;
// ... additional computed property rules
}Comprehensive testing rules for modern Ember testing patterns and test helper usage.
interface TestRules {
'ember/no-legacy-test-waiters': ESLintRule;
'ember/no-test-and-then': ESLintRule;
'ember/prefer-ember-test-helpers': ESLintRule;
'ember/require-valid-css-selector-in-test-helpers': ESLintRule;
// ... additional test rules
}Rules helping with Ember version migrations and deprecation warnings.
interface MigrationRules {
'ember/classic-decorator-hooks': ESLintRule;
'ember/new-module-imports': ESLintRule;
'ember/no-deprecated-router-transition-methods': ESLintRule;
'ember/no-get': ESLintRule;
'ember/no-old-shims': ESLintRule;
// ... additional migration rules
}Comprehensive utility functions for AST analysis and Ember-specific pattern detection.
interface EmberUtils {
// Core module detection
isDSModel(node: ASTNode, filePath?: string): boolean;
isEmberComponent(context: ESLintContext, node: ASTNode): boolean;
isEmberController(context: ESLintContext, node: ASTNode): boolean;
isEmberRoute(context: ESLintContext, node: ASTNode): boolean;
isEmberService(context: ESLintContext, node: ASTNode): boolean;
// Property detection
isComputedProp(node: ASTNode, importedEmberName: string, importedComputedName: string, options?: ComputedPropOptions): boolean;
isInjectedServiceProp(node: ASTNode, importedEmberName: string, importedInjectName: string): boolean;
isObserverProp(node: ASTNode, importedEmberName: string, importedObserverName: string): boolean;
// Lifecycle hooks
isRouteLifecycleHook(property: ASTNode): boolean;
isComponentLifecycleHook(property: ASTNode): boolean;
// Analysis utilities
parseDependentKeys(callExp: ASTNode): string[];
getModuleProperties(moduleNode: ASTNode, scopeManager: ScopeManager): ASTNode[];
// ... additional utility functions
}interface ESLintRule {
meta: ESLintRuleMeta;
create: (context: ESLintContext) => ESLintRuleVisitor;
}
interface ESLintRuleMeta {
type: 'problem' | 'suggestion' | 'layout';
docs: {
description: string;
category: string;
recommended: boolean;
url: string;
};
fixable?: 'code' | 'whitespace' | null;
schema: JSONSchema[];
}
interface ESLintRuleConfig {
[ruleName: string]: 'off' | 'warn' | 'error' | [string, ...any[]];
}
interface ESLintContext {
getFilename?(): string;
getSourceCode(): SourceCode;
report(descriptor: ReportDescriptor): void;
}
interface ComputedPropOptions {
includeSuffix?: boolean;
includeMacro?: boolean;
}
interface LanguageOptions {
parser?: ESLintParser;
parserOptions?: Record<string, any>;
}
type ASTNode = any; // ESTree AST Node
type ScopeManager = any; // ESLint Scope Manager
type ESLintParser = any; // ESLint Parser
type ESLintProcessor = any; // ESLint Processor
type ESLintConfig = any; // ESLint Configuration
type ESLintRuleVisitor = any; // ESLint Rule Visitor
type SourceCode = any; // ESLint SourceCode
type ReportDescriptor = any; // ESLint Report Descriptor
type JSONSchema = any; // JSON Schema