Skip to content

Port 125 ember-template-lint rules for gjs/gts strict mode templates #3749

Port 125 ember-template-lint rules for gjs/gts strict mode templates

Port 125 ember-template-lint rules for gjs/gts strict mode templates #3749

Re-run triggered January 28, 2026 04:51
Status Failure
Total duration 1m 20s
Artifacts

ci.yml

on: pull_request
self-lint
32s
self-lint
Matrix: build
Fit to window
Zoom out
Zoom in

Annotations

48 errors
self-lint: lib/rules/template-no-invalid-aria-attributes.js#L13
The meta properties should be placed in a consistent order: [type, docs, schema, messages]
self-lint: lib/rules/template-no-html-comments.js#L29
The placeholder {{!-- --}} is missing (must provide it in the report's `data` object)
self-lint: lib/rules/template-no-html-comments.js#L29
The placeholder {{!}} is missing (must provide it in the report's `data` object)
self-lint: lib/rules/template-no-html-comments.js#L17
Insert `⏎·······`
self-lint: lib/rules/template-no-html-comments.js#L15
The meta properties should be placed in a consistent order: [type, docs, schema, messages]
self-lint: lib/rules/template-no-heading-inside-button.js#L20
Move function 'hasButtonParent' to the outer scope
self-lint: lib/rules/template-no-empty-headings.js#L30
Replace `⏎··········child.type·===·'GlimmerMustacheStatement'·||⏎··········child.type·===·'GlimmerBlockStatement'⏎········` with `child.type·===·'GlimmerMustacheStatement'·||·child.type·===·'GlimmerBlockStatement'`
self-lint: lib/rules/template-no-empty-headings.js#L22
Expected { after 'if' condition
self-lint: lib/rules/template-no-empty-headings.js#L21
Move function 'hasTextContent' to the outer scope
self-lint: lib/rules/template-no-bare-yield.js#L13
The meta properties should be placed in a consistent order: [type, docs, schema, messages]
tests/rule-setup.js > rules setup is correct > should mention all rules in the README: tests/rule-setup.js#L81
AssertionError: expected '# eslint-plugin-ember\n\n[![NPM versi…' to contain 'template-no-invalid-aria-attributes' - Expected + Received - template-no-invalid-aria-attributes + # eslint-plugin-ember + + [![NPM version](https://img.shields.io/npm/v/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember) + [![NPM downloads](https://img.shields.io/npm/dm/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember) + ![CI](https://github.com/ember-cli/eslint-plugin-ember/workflows/CI/badge.svg) + + > An ESLint plugin that provides a set of rules for Ember applications based on commonly known good practices. + + ## ❗️Requirements + + - [ESLint](https://eslint.org/) `>= 8` + - [Node.js](https://nodejs.org/) `18.* || 20.* || >= 21` + + ## 🚀 Usage + + ### 1. Install plugin + + ```shell + npm install --save-dev eslint-plugin-ember + ``` + + ### 2. Update your config + + ```js + // eslint.config.js (flat config) + const eslintPluginEmberRecommended = require('eslint-plugin-ember/configs/recommended'); + + module.exports = [ + ...eslintPluginEmberRecommended, + ]; + ``` + + or + + ```js + // .eslintrc.js (legacy config) + module.exports = { + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended' // or other configuration + ], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }; + ``` + + ## gts/gjs + + lint files having `First-Class Component Templates` (fcct) + + learn more [here](https://github.com/ember-template-imports/ember-template-imports) + + > [!NOTE] + > special care should be used when setting up parsers, since they cannot be overwritten. thus they should be used in override only and specific to file types + + gjs/gts support is provided by the [ember-eslint-parser](https://github.com/NullVoxPopuli/ember-eslint-parser) + + > [!NOTE] + > if you import .gts files in .ts files, then `ember-eslint-parser` is required for .ts as well to enable typed linting + + ```js + // .eslintrc.js + module.exports = { + overrides: [ + { + files: ['**/*.{js,ts}'], + plugins: ['ember'], + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', // or other configuration + ], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }, + { + files: ['**/*.gts'], + parser: 'ember-eslint-parser', + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:ember/recommended', + 'plugin:ember/recommended-gts', + ], + }, + { + files: ['**/*.gjs'], + parser: 'ember-eslint-parser', + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', + 'plugin:ember/recommended-gjs', + ], + }, + { + files: ['tests/**/*.{js,ts,gjs,gts}'], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }, + ], + }; + ``` + + ### Strict Template Linting (Optional) + + For enhanced template linting in `gjs`/`gts` files, use the `strict-gjs` or `strict-gts` configs. These include additional template rules ported from `ember-template-lint`: + + ```javascript + // eslint.config.js + const ember = require('eslint-plugin-ember'); + + module.exports = [ + ...ember.configs.recommended, + { + files: ['**/*.gts'], + extends: ['plugin:ember/strict-gts'], + }, + { + files: ['**/*.gjs'], + extends: ['plugin:ember/strict-gjs'], + }, + ]; + ``` + + + ### rules applied to fcct templates + + - semi rule, same as [prettier plugin](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1) + - no-undef rule will take effect for template vars (includes js scope) + - no-unused rule will take effect for template block
tests/rule-setup.js > rules setup is correct > rule files > template-simple-unless > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require simple conditions in unless blocks', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-simple-unless.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + simpleUnless: + 'unless blocks should use simple conditions. Use if with negated condition for complex logic.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'unless') { + if (node.params.length > 0) { + const firstParam = node.params[0]; + if (firstParam.type === 'GlimmerSubExpression') { + context.report({ + node: firstParam, + messageId: 'simpleUnless', + }); + } + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-require-each-key > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'require key attribute in {{#each}} loops', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-each-key.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + requireEachKey: 'each block should have a key attribute for better rendering performance.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'each') { + const hasKey = node.hash && node.hash.pairs.some((pair) => pair.key === 'key'); + if (!hasKey) { + context.report({ + node, + messageId: 'requireEachKey', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-unnecessary-curly-in-string-attrs > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unnecessary curly braces in attributes', + category: 'Style', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-curly-in-string-attrs.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + unnecessaryCurlyInStringAttr: + 'Unnecessary curly braces around string attribute. Use static string instead.', + }, + }, + + create(context) { + return { + GlimmerAttrNode(node) { + if ( + node.value.type === 'GlimmerMustacheStatement' && + node.value.path.type === 'GlimmerStringLiteral' + ) { + context.report({ + node: node.value, + messageId: 'unnecessaryCurlyInStringAttr', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-outlet-outside-routes > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow {{outlet}} outside of route templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-outlet-outside-routes.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noOutletOutsideRoutes: 'outlet should only be used in route templates.', + }, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path.type === 'GlimmerPathExpression' && + node.path.original === 'outlet' + ) { + context.report({ + node, + messageId: 'noOutletOutsideRoutes', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-negated-condition > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow negated conditions in if/unless', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-negated-condition.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noNegatedCondition: + 'Unexpected negated condition. Use unless helper or rewrite condition.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'if') { + const firstParam = node.params[0]; + if ( + firstParam && + firstParam.type === 'GlimmerSubExpression' && + firstParam.path.type === 'GlimmerPathExpression' && + firstParam.path.original === 'not' + ) { + context.report({ + node: firstParam, + messageId: 'noNegatedCondition', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-invalid-link-title > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow invalid title attributes on link elements', + category: 'Accessibility', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-link-title.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noInvalidLinkTitle: + 'Link title attribute should not be the same as link text or empty.', + }, + }, + + create(context) { + return { + GlimmerElementNode(node) { + if (node.tag !== 'a') { + return; + } + + const titleAttr = node.attributes.find( + (attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'title' + ); + + if (!titleAttr || titleAttr.value.type !== 'GlimmerTextNode') { + return; + } + + const titleValue = titleAttr.value.chars.trim(); + + if (!titleValue) { + context.report({ + node: titleAttr, + messageId: 'noInvalidLinkTitle', + }); + return; + } + + if (node.children.length === 1 && node.children[0].type === 'GlimmerTextNode') { + const textContent = node.children[0].chars.trim(); + if (textContent && textContent === titleValue) { + context.report({ + node: titleAttr, + messageId: 'noInvalidLinkTitle', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-invalid-aria-attributes > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow invalid aria-* attributes', + category: 'Accessibility', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-aria-attributes.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noInvalidAriaAttribute: 'Invalid ARIA attribute: {{attribute}}', + }, + }, + + create(context) { + const validAriaAttributes = [ + 'aria-activedescendant', + 'aria-atomic', + 'aria-autocomplete', + 'aria-busy', + 'aria-checked', + 'aria-colcount', + 'aria-colindex', + 'aria-colspan', + 'aria-controls', + 'aria-current', + 'aria-describedby', + 'aria-details', + 'aria-disabled', + 'aria-dropeffect', + 'aria-errormessage', + 'aria-expanded', + 'aria-flowto', + 'aria-grabbed', + 'aria-haspopup', + 'aria-hidden', + 'aria-invalid', + 'aria-keyshortcuts', + 'aria-label', + 'aria-labelledby', + 'aria-level', + 'aria-live', + 'aria-modal', + 'aria-multiline', + 'aria-multiselectable', + 'aria-orientation', + 'aria-owns', + 'aria-placeholder', + 'aria-posinset', + 'aria-pressed', + 'aria-readonly', + 'aria-relevant', + 'aria-required', + 'aria-roledescription', + 'aria-rowcount', + 'aria-rowindex', + 'aria-rowspan', + 'aria-selected', + 'aria-setsize', + 'aria-sort', + 'aria-valuemax', + 'aria-valuemin', + 'aria-valuenow', + 'aria-valuetext', + ]; + + return { + GlimmerAttrNode(node) { + if (node.name.startsWith('aria-')) { + if (!validAriaAttributes.includes(node.name)) { + context.report({ + node, + messageId: 'noInvalidAriaAttribute', + data: { attribute: node.name }, + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-html-comments > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nconst { getSourceC…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + const { getSourceCode } = require('../utils/utils'); + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow HTML comments in templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-html-comments.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noHtmlComments: 'HTML comments should not be used in templates. Use {{! }} or {{!-- --}} instead.', + }, + }, + + create(context) { + const sourceCode = getSourceCode(context); + + return { + GlimmerCommentStatement(node) { + if (node.value && node.value.startsWith('<!--')) { + context.report({ + node, + messageId: 'noHtmlComments', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-bare-yield > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow {{yield}} without parameters outside of contextual components', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-bare-yield.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noBareYield: 'yield should have parameters or be used in contextual components only.', + }, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path.type === 'GlimmerPathExpression' && + node.path.original === 'yield' && + (!node.params || node.params.length === 0) + ) { + context.report({ + node, + messageId: 'noBareYield', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
build (ubuntu, 20.x)
The strategy configuration was canceled because "build.ubuntu_22_x" failed
tests/rule-setup.js > rules setup is correct > should mention all rules in the README: tests/rule-setup.js#L81
AssertionError: expected '# eslint-plugin-ember\n\n[![NPM versi…' to contain 'template-no-invalid-aria-attributes' - Expected + Received - template-no-invalid-aria-attributes + # eslint-plugin-ember + + [![NPM version](https://img.shields.io/npm/v/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember) + [![NPM downloads](https://img.shields.io/npm/dm/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember) + ![CI](https://github.com/ember-cli/eslint-plugin-ember/workflows/CI/badge.svg) + + > An ESLint plugin that provides a set of rules for Ember applications based on commonly known good practices. + + ## ❗️Requirements + + - [ESLint](https://eslint.org/) `>= 8` + - [Node.js](https://nodejs.org/) `18.* || 20.* || >= 21` + + ## 🚀 Usage + + ### 1. Install plugin + + ```shell + npm install --save-dev eslint-plugin-ember + ``` + + ### 2. Update your config + + ```js + // eslint.config.js (flat config) + const eslintPluginEmberRecommended = require('eslint-plugin-ember/configs/recommended'); + + module.exports = [ + ...eslintPluginEmberRecommended, + ]; + ``` + + or + + ```js + // .eslintrc.js (legacy config) + module.exports = { + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended' // or other configuration + ], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }; + ``` + + ## gts/gjs + + lint files having `First-Class Component Templates` (fcct) + + learn more [here](https://github.com/ember-template-imports/ember-template-imports) + + > [!NOTE] + > special care should be used when setting up parsers, since they cannot be overwritten. thus they should be used in override only and specific to file types + + gjs/gts support is provided by the [ember-eslint-parser](https://github.com/NullVoxPopuli/ember-eslint-parser) + + > [!NOTE] + > if you import .gts files in .ts files, then `ember-eslint-parser` is required for .ts as well to enable typed linting + + ```js + // .eslintrc.js + module.exports = { + overrides: [ + { + files: ['**/*.{js,ts}'], + plugins: ['ember'], + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', // or other configuration + ], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }, + { + files: ['**/*.gts'], + parser: 'ember-eslint-parser', + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:ember/recommended', + 'plugin:ember/recommended-gts', + ], + }, + { + files: ['**/*.gjs'], + parser: 'ember-eslint-parser', + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', + 'plugin:ember/recommended-gjs', + ], + }, + { + files: ['tests/**/*.{js,ts,gjs,gts}'], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }, + ], + }; + ``` + + ### Strict Template Linting (Optional) + + For enhanced template linting in `gjs`/`gts` files, use the `strict-gjs` or `strict-gts` configs. These include additional template rules ported from `ember-template-lint`: + + ```javascript + // eslint.config.js + const ember = require('eslint-plugin-ember'); + + module.exports = [ + ...ember.configs.recommended, + { + files: ['**/*.gts'], + extends: ['plugin:ember/strict-gts'], + }, + { + files: ['**/*.gjs'], + extends: ['plugin:ember/strict-gjs'], + }, + ]; + ``` + + + ### rules applied to fcct templates + + - semi rule, same as [prettier plugin](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1) + - no-undef rule will take effect for template vars (includes js scope) + - no-unused rule will take effect for template block
tests/rule-setup.js > rules setup is correct > rule files > template-simple-unless > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require simple conditions in unless blocks', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-simple-unless.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + simpleUnless: + 'unless blocks should use simple conditions. Use if with negated condition for complex logic.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'unless') { + if (node.params.length > 0) { + const firstParam = node.params[0]; + if (firstParam.type === 'GlimmerSubExpression') { + context.report({ + node: firstParam, + messageId: 'simpleUnless', + }); + } + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-require-each-key > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'require key attribute in {{#each}} loops', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-each-key.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + requireEachKey: 'each block should have a key attribute for better rendering performance.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'each') { + const hasKey = node.hash && node.hash.pairs.some((pair) => pair.key === 'key'); + if (!hasKey) { + context.report({ + node, + messageId: 'requireEachKey', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-unnecessary-curly-in-string-attrs > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unnecessary curly braces in attributes', + category: 'Style', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-curly-in-string-attrs.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + unnecessaryCurlyInStringAttr: + 'Unnecessary curly braces around string attribute. Use static string instead.', + }, + }, + + create(context) { + return { + GlimmerAttrNode(node) { + if ( + node.value.type === 'GlimmerMustacheStatement' && + node.value.path.type === 'GlimmerStringLiteral' + ) { + context.report({ + node: node.value, + messageId: 'unnecessaryCurlyInStringAttr', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-outlet-outside-routes > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow {{outlet}} outside of route templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-outlet-outside-routes.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noOutletOutsideRoutes: 'outlet should only be used in route templates.', + }, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path.type === 'GlimmerPathExpression' && + node.path.original === 'outlet' + ) { + context.report({ + node, + messageId: 'noOutletOutsideRoutes', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-negated-condition > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow negated conditions in if/unless', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-negated-condition.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noNegatedCondition: + 'Unexpected negated condition. Use unless helper or rewrite condition.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'if') { + const firstParam = node.params[0]; + if ( + firstParam && + firstParam.type === 'GlimmerSubExpression' && + firstParam.path.type === 'GlimmerPathExpression' && + firstParam.path.original === 'not' + ) { + context.report({ + node: firstParam, + messageId: 'noNegatedCondition', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-invalid-link-title > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow invalid title attributes on link elements', + category: 'Accessibility', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-link-title.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noInvalidLinkTitle: + 'Link title attribute should not be the same as link text or empty.', + }, + }, + + create(context) { + return { + GlimmerElementNode(node) { + if (node.tag !== 'a') { + return; + } + + const titleAttr = node.attributes.find( + (attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'title' + ); + + if (!titleAttr || titleAttr.value.type !== 'GlimmerTextNode') { + return; + } + + const titleValue = titleAttr.value.chars.trim(); + + if (!titleValue) { + context.report({ + node: titleAttr, + messageId: 'noInvalidLinkTitle', + }); + return; + } + + if (node.children.length === 1 && node.children[0].type === 'GlimmerTextNode') { + const textContent = node.children[0].chars.trim(); + if (textContent && textContent === titleValue) { + context.report({ + node: titleAttr, + messageId: 'noInvalidLinkTitle', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-invalid-aria-attributes > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow invalid aria-* attributes', + category: 'Accessibility', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-aria-attributes.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noInvalidAriaAttribute: 'Invalid ARIA attribute: {{attribute}}', + }, + }, + + create(context) { + const validAriaAttributes = [ + 'aria-activedescendant', + 'aria-atomic', + 'aria-autocomplete', + 'aria-busy', + 'aria-checked', + 'aria-colcount', + 'aria-colindex', + 'aria-colspan', + 'aria-controls', + 'aria-current', + 'aria-describedby', + 'aria-details', + 'aria-disabled', + 'aria-dropeffect', + 'aria-errormessage', + 'aria-expanded', + 'aria-flowto', + 'aria-grabbed', + 'aria-haspopup', + 'aria-hidden', + 'aria-invalid', + 'aria-keyshortcuts', + 'aria-label', + 'aria-labelledby', + 'aria-level', + 'aria-live', + 'aria-modal', + 'aria-multiline', + 'aria-multiselectable', + 'aria-orientation', + 'aria-owns', + 'aria-placeholder', + 'aria-posinset', + 'aria-pressed', + 'aria-readonly', + 'aria-relevant', + 'aria-required', + 'aria-roledescription', + 'aria-rowcount', + 'aria-rowindex', + 'aria-rowspan', + 'aria-selected', + 'aria-setsize', + 'aria-sort', + 'aria-valuemax', + 'aria-valuemin', + 'aria-valuenow', + 'aria-valuetext', + ]; + + return { + GlimmerAttrNode(node) { + if (node.name.startsWith('aria-')) { + if (!validAriaAttributes.includes(node.name)) { + context.report({ + node, + messageId: 'noInvalidAriaAttribute', + data: { attribute: node.name }, + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-html-comments > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nconst { getSourceC…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + const { getSourceCode } = require('../utils/utils'); + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow HTML comments in templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-html-comments.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noHtmlComments: 'HTML comments should not be used in templates. Use {{! }} or {{!-- --}} instead.', + }, + }, + + create(context) { + const sourceCode = getSourceCode(context); + + return { + GlimmerCommentStatement(node) { + if (node.value && node.value.startsWith('<!--')) { + context.report({ + node, + messageId: 'noHtmlComments', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-bare-yield > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow {{yield}} without parameters outside of contextual components', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-bare-yield.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noBareYield: 'yield should have parameters or be used in contextual components only.', + }, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path.type === 'GlimmerPathExpression' && + node.path.original === 'yield' && + (!node.params || node.params.length === 0) + ) { + context.report({ + node, + messageId: 'noBareYield', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
build (ubuntu, 18.x)
The strategy configuration was canceled because "build.ubuntu_22_x" failed
tests/rule-setup.js > rules setup is correct > should mention all rules in the README: tests/rule-setup.js#L81
AssertionError: expected '# eslint-plugin-ember\n\n[![NPM versi…' to contain 'template-no-invalid-aria-attributes' - Expected + Received - template-no-invalid-aria-attributes + # eslint-plugin-ember + + [![NPM version](https://img.shields.io/npm/v/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember) + [![NPM downloads](https://img.shields.io/npm/dm/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember) + ![CI](https://github.com/ember-cli/eslint-plugin-ember/workflows/CI/badge.svg) + + > An ESLint plugin that provides a set of rules for Ember applications based on commonly known good practices. + + ## ❗️Requirements + + - [ESLint](https://eslint.org/) `>= 8` + - [Node.js](https://nodejs.org/) `18.* || 20.* || >= 21` + + ## 🚀 Usage + + ### 1. Install plugin + + ```shell + npm install --save-dev eslint-plugin-ember + ``` + + ### 2. Update your config + + ```js + // eslint.config.js (flat config) + const eslintPluginEmberRecommended = require('eslint-plugin-ember/configs/recommended'); + + module.exports = [ + ...eslintPluginEmberRecommended, + ]; + ``` + + or + + ```js + // .eslintrc.js (legacy config) + module.exports = { + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended' // or other configuration + ], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }; + ``` + + ## gts/gjs + + lint files having `First-Class Component Templates` (fcct) + + learn more [here](https://github.com/ember-template-imports/ember-template-imports) + + > [!NOTE] + > special care should be used when setting up parsers, since they cannot be overwritten. thus they should be used in override only and specific to file types + + gjs/gts support is provided by the [ember-eslint-parser](https://github.com/NullVoxPopuli/ember-eslint-parser) + + > [!NOTE] + > if you import .gts files in .ts files, then `ember-eslint-parser` is required for .ts as well to enable typed linting + + ```js + // .eslintrc.js + module.exports = { + overrides: [ + { + files: ['**/*.{js,ts}'], + plugins: ['ember'], + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', // or other configuration + ], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }, + { + files: ['**/*.gts'], + parser: 'ember-eslint-parser', + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:ember/recommended', + 'plugin:ember/recommended-gts', + ], + }, + { + files: ['**/*.gjs'], + parser: 'ember-eslint-parser', + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', + 'plugin:ember/recommended-gjs', + ], + }, + { + files: ['tests/**/*.{js,ts,gjs,gts}'], + rules: { + // override / enable optional rules + 'ember/no-replace-test-comments': 'error' + } + }, + ], + }; + ``` + + ### Strict Template Linting (Optional) + + For enhanced template linting in `gjs`/`gts` files, use the `strict-gjs` or `strict-gts` configs. These include additional template rules ported from `ember-template-lint`: + + ```javascript + // eslint.config.js + const ember = require('eslint-plugin-ember'); + + module.exports = [ + ...ember.configs.recommended, + { + files: ['**/*.gts'], + extends: ['plugin:ember/strict-gts'], + }, + { + files: ['**/*.gjs'], + extends: ['plugin:ember/strict-gjs'], + }, + ]; + ``` + + + ### rules applied to fcct templates + + - semi rule, same as [prettier plugin](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1) + - no-undef rule will take effect for template vars (includes js scope) + - no-unused rule will take effect for template block
tests/rule-setup.js > rules setup is correct > rule files > template-simple-unless > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require simple conditions in unless blocks', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-simple-unless.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + simpleUnless: + 'unless blocks should use simple conditions. Use if with negated condition for complex logic.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'unless') { + if (node.params.length > 0) { + const firstParam = node.params[0]; + if (firstParam.type === 'GlimmerSubExpression') { + context.report({ + node: firstParam, + messageId: 'simpleUnless', + }); + } + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-require-each-key > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'require key attribute in {{#each}} loops', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-each-key.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + requireEachKey: 'each block should have a key attribute for better rendering performance.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'each') { + const hasKey = node.hash && node.hash.pairs.some((pair) => pair.key === 'key'); + if (!hasKey) { + context.report({ + node, + messageId: 'requireEachKey', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-unnecessary-curly-in-string-attrs > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unnecessary curly braces in attributes', + category: 'Style', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-curly-in-string-attrs.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + unnecessaryCurlyInStringAttr: + 'Unnecessary curly braces around string attribute. Use static string instead.', + }, + }, + + create(context) { + return { + GlimmerAttrNode(node) { + if ( + node.value.type === 'GlimmerMustacheStatement' && + node.value.path.type === 'GlimmerStringLiteral' + ) { + context.report({ + node: node.value, + messageId: 'unnecessaryCurlyInStringAttr', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-outlet-outside-routes > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow {{outlet}} outside of route templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-outlet-outside-routes.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noOutletOutsideRoutes: 'outlet should only be used in route templates.', + }, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path.type === 'GlimmerPathExpression' && + node.path.original === 'outlet' + ) { + context.report({ + node, + messageId: 'noOutletOutsideRoutes', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-negated-condition > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow negated conditions in if/unless', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-negated-condition.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noNegatedCondition: + 'Unexpected negated condition. Use unless helper or rewrite condition.', + }, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'if') { + const firstParam = node.params[0]; + if ( + firstParam && + firstParam.type === 'GlimmerSubExpression' && + firstParam.path.type === 'GlimmerPathExpression' && + firstParam.path.original === 'not' + ) { + context.report({ + node: firstParam, + messageId: 'noNegatedCondition', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-invalid-link-title > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow invalid title attributes on link elements', + category: 'Accessibility', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-link-title.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noInvalidLinkTitle: + 'Link title attribute should not be the same as link text or empty.', + }, + }, + + create(context) { + return { + GlimmerElementNode(node) { + if (node.tag !== 'a') { + return; + } + + const titleAttr = node.attributes.find( + (attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'title' + ); + + if (!titleAttr || titleAttr.value.type !== 'GlimmerTextNode') { + return; + } + + const titleValue = titleAttr.value.chars.trim(); + + if (!titleValue) { + context.report({ + node: titleAttr, + messageId: 'noInvalidLinkTitle', + }); + return; + } + + if (node.children.length === 1 && node.children[0].type === 'GlimmerTextNode') { + const textContent = node.children[0].chars.trim(); + if (textContent && textContent === titleValue) { + context.report({ + node: titleAttr, + messageId: 'noInvalidLinkTitle', + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-invalid-aria-attributes > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow invalid aria-* attributes', + category: 'Accessibility', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-aria-attributes.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noInvalidAriaAttribute: 'Invalid ARIA attribute: {{attribute}}', + }, + }, + + create(context) { + const validAriaAttributes = [ + 'aria-activedescendant', + 'aria-atomic', + 'aria-autocomplete', + 'aria-busy', + 'aria-checked', + 'aria-colcount', + 'aria-colindex', + 'aria-colspan', + 'aria-controls', + 'aria-current', + 'aria-describedby', + 'aria-details', + 'aria-disabled', + 'aria-dropeffect', + 'aria-errormessage', + 'aria-expanded', + 'aria-flowto', + 'aria-grabbed', + 'aria-haspopup', + 'aria-hidden', + 'aria-invalid', + 'aria-keyshortcuts', + 'aria-label', + 'aria-labelledby', + 'aria-level', + 'aria-live', + 'aria-modal', + 'aria-multiline', + 'aria-multiselectable', + 'aria-orientation', + 'aria-owns', + 'aria-placeholder', + 'aria-posinset', + 'aria-pressed', + 'aria-readonly', + 'aria-relevant', + 'aria-required', + 'aria-roledescription', + 'aria-rowcount', + 'aria-rowindex', + 'aria-rowspan', + 'aria-selected', + 'aria-setsize', + 'aria-sort', + 'aria-valuemax', + 'aria-valuemin', + 'aria-valuenow', + 'aria-valuetext', + ]; + + return { + GlimmerAttrNode(node) { + if (node.name.startsWith('aria-')) { + if (!validAriaAttributes.includes(node.name)) { + context.report({ + node, + messageId: 'noInvalidAriaAttribute', + data: { attribute: node.name }, + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-html-comments > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nconst { getSourceC…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + const { getSourceCode } = require('../utils/utils'); + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow HTML comments in templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-html-comments.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noHtmlComments: 'HTML comments should not be used in templates. Use {{! }} or {{!-- --}} instead.', + }, + }, + + create(context) { + const sourceCode = getSourceCode(context); + + return { + GlimmerCommentStatement(node) { + if (node.value && node.value.startsWith('<!--')) { + context.report({ + node, + messageId: 'noHtmlComments', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-bare-yield > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '\'use strict\';\n\nmodule.exports = {…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + 'use strict'; + + module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow {{yield}} without parameters outside of contextual components', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-bare-yield.md', + }, + strictGjs: true, + strictGts: true, + schema: [], + messages: { + noBareYield: 'yield should have parameters or be used in contextual components only.', + }, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path.type === 'GlimmerPathExpression' && + node.path.original === 'yield' && + (!node.params || node.params.length === 0) + ) { + context.report({ + node, + messageId: 'noBareYield', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
build (windows, 18.x)
The strategy configuration was canceled because "build.ubuntu_22_x" failed
build (windows, 18.x)
The operation was canceled.
build (windows, 20.x)
The strategy configuration was canceled because "build.ubuntu_22_x" failed
build (windows, 20.x)
The operation was canceled.
build (windows, 22.x)
The strategy configuration was canceled because "build.ubuntu_22_x" failed
build (windows, 22.x)
The operation was canceled.