Linter for Ember or Handlebars templates.
npx @tessl/cli install tessl/npm-ember-template-lint@7.9.0Ember Template Lint is a comprehensive linting tool specifically designed for Ember.js and Handlebars templates. It provides a robust rule engine with configurable linting rules that can detect common anti-patterns, accessibility issues, deprecated syntax, and style violations in Handlebars templates.
npm install ember-template-lintimport Linter, { Rule, ASTHelpers, NodeMatcher, generateRuleTests, recast } from "ember-template-lint";For CommonJS:
const Linter = require("ember-template-lint").default;
const { Rule, ASTHelpers, NodeMatcher, generateRuleTests, recast } = require("ember-template-lint");import Linter from "ember-template-lint";
// Initialize linter with configuration
const linter = new Linter({
workingDir: process.cwd(),
console: console
});
// Load configuration (async)
await linter.loadConfig();
// Lint a template
const results = await linter.verify({
source: '<div class="my-class">{{message}}</div>',
filePath: 'app/templates/example.hbs'
});
// Process results
if (results.length > 0) {
console.log('Linting violations found:');
results.forEach(result => {
console.log(`${result.rule}: ${result.message} (${result.line}:${result.column})`);
});
}Ember Template Lint is built around several key components:
Main linting functionality for processing Handlebars templates with configurable rules and output formats.
class Linter {
constructor(options?: LinterOptions): Linter;
loadConfig(): Promise<void>;
verify(options: VerifyOptions): Promise<LintResult[]>;
verifyAndFix(options: VerifyOptions): Promise<FixResult>;
}
interface LinterOptions {
workingDir?: string;
console?: Console;
rule?: string;
configPath?: string;
allowInlineConfig?: boolean;
reportUnusedDisableDirectives?: boolean;
checkHbsTemplateLiterals?: boolean;
}
interface VerifyOptions {
source: string;
filePath: string;
workingDir?: string;
configResolver?: ConfigResolver;
}
interface LintResult {
rule: string;
message: string;
line: number;
column: number;
severity: number;
source?: string;
fix?: FixInfo;
}Framework for creating custom linting rules with AST visitor patterns and testing utilities.
class Rule {
constructor(options: RuleOptions): Rule;
visitor(): VisitorMethods;
log(result: RuleResult): void;
}
interface RuleOptions {
name: string;
config: any;
console: Console;
defaultSeverity: number;
workingDir: string;
ruleNames: string[];
allowInlineConfig: boolean;
reportUnusedDisableDirectives: boolean;
}
function generateRuleTests(options: RuleTestOptions): void;Comprehensive set of 132+ linting rules covering accessibility, style, best practices, and Ember-specific patterns.
// Rule categories available
type RuleCategory =
| "accessibility" // ARIA, semantic HTML, keyboard navigation
| "components" // Ember component best practices
| "style" // Formatting and code style
| "deprecated" // Deprecated Ember patterns
| "security" // Security-related checks
| "performance" // Performance optimizations
| "maintainability"; // Code maintainabilityFlexible configuration system supporting presets, custom rules, plugins, and file-specific overrides.
function getProjectConfig(workingDir: string, options?: ConfigOptions): Promise<ProjectConfig>;
interface ProjectConfig {
rules: Record<string, RuleConfig>;
extends?: string | string[];
plugins?: string[];
ignore?: string[];
overrides?: Override[];
format?: FormatConfig;
reportUnusedDisableDirectives?: boolean;
checkHbsTemplateLiterals?: boolean;
}
interface RuleConfig {
severity: -1 | 0 | 1 | 2; // todo, ignore, warn, error
config?: any;
}Utilities for analyzing and manipulating Handlebars AST nodes during rule execution.
class ASTHelpers {
static isConfigurationHtmlComment(node: CommentStatement): boolean;
static isIf(node: MustacheStatement | BlockStatement | SubExpression): boolean;
static isUnless(node: MustacheStatement | BlockStatement | SubExpression): boolean;
static isEach(node: MustacheStatement | BlockStatement | SubExpression): boolean;
// ... additional AST analysis methods
}
class NodeMatcher {
static match(testNode: Node, referenceNode: Node | Node[]): boolean;
}Comprehensive CLI for integrating with development workflows and CI/CD pipelines.
// Command line options interface
interface CLIOptions {
configPath?: string; // Custom config file path
config?: string; // Inline JSON configuration
quiet?: boolean; // Suppress warnings, show only errors
rule?: string; // Single rule override "rule-name:severity"
filename?: string; // Filename for stdin input
fix?: boolean; // Auto-fix issues where possible
format?: string; // Output format: "pretty", "json", "sarif", "kakoune"
printFullPath?: boolean; // Print absolute file paths
outputFile?: string; // Write output to file
verbose?: boolean; // Include source descriptions
workingDirectory?: string; // Working directory
noConfigPath?: boolean; // Disable config file loading
updateTodo?: boolean; // Update TODO list
includeTodo?: boolean; // Include TODOs in output
cleanTodo?: boolean; // Clean expired TODOs
compactTodo?: boolean; // Compact TODO storage
todoDaysToWarn?: number; // Days until TODO becomes warning
todoDaysToError?: number; // Days until TODO becomes error
ignorePattern?: string[]; // Ignore patterns
noInlineConfig?: boolean; // Disable inline config comments
printConfig?: boolean; // Print effective configuration
maxWarnings?: number; // Max warnings before exit 1
noErrorOnUnmatchedPattern?: boolean; // Don't error on unmatched patterns
reportUnusedDisableDirectives?: boolean; // Report unused disable directives
}Multiple output formats for linting results supporting console output, JSON, SARIF format, and editor integration.
// Formatter classes (no shared base class)
class PrettyFormatter {
constructor(options?: FormatterOptions): PrettyFormatter;
format(results: LintResult[], todoInfo?: TodoInfo): string;
}
class JsonFormatter {
constructor(options?: FormatterOptions): JsonFormatter;
format(results: LintResult[], todoInfo?: TodoInfo): string;
defaultFileExtension: 'json';
}
class SarifFormatter {
constructor(options?: FormatterOptions): SarifFormatter;
format(results: LintResult[], todoInfo?: TodoInfo): string;
defaultFileExtension: 'sarif';
}
class KakouneFormatter {
constructor(options?: FormatterOptions): KakouneFormatter;
format(results: LintResult[], todoInfo?: TodoInfo): string;
}// Severity levels
const TODO_SEVERITY = -1;
const IGNORE_SEVERITY = 0;
const WARNING_SEVERITY = 1;
const ERROR_SEVERITY = 2;
// Formatter options
interface FormatterOptions {
printFullPath?: boolean; // Print absolute file paths
includeTodo?: boolean; // Include TODO items in output
outputFile?: string; // Output file path
quiet?: boolean; // Suppress warnings
updateTodo?: boolean; // Whether updating TODOs
verbose?: boolean; // Verbose output mode
hasResultData?: boolean; // Whether results contain data
config?: any; // Configuration object
workingDirectory?: string; // Working directory path
console?: Console; // Console object for output
isInteractive?: boolean; // Interactive mode flag
}
// TODO information
interface TodoInfo {
add: number; // Number of TODOs added
remove: number; // Number of TODOs removed
stable: number; // Number of stable TODOs
expired: number; // Number of expired TODOs
}
// AST Node types (from ember-template-recast)
interface Template {
type: "Template";
body: Statement[];
blockParams: string[];
loc: SourceLocation;
}
interface ElementNode {
type: "ElementNode";
tag: string;
attributes: AttrNode[];
blockParams: string[];
modifiers: ElementModifierStatement[];
comments: MustacheCommentStatement[];
children: Statement[];
loc: SourceLocation;
}
interface MustacheStatement {
type: "MustacheStatement";
path: PathExpression;
params: Expression[];
hash: Hash;
escaped: boolean;
loc: SourceLocation;
}
interface BlockStatement {
type: "BlockStatement";
path: PathExpression;
params: Expression[];
hash: Hash;
program: Program;
inverse?: Program;
loc: SourceLocation;
}
interface SourceLocation {
start: Position;
end: Position;
}
interface Position {
line: number;
column: number;
}