Skip to content

Addressing comment on PR #2365 #16

Addressing comment on PR #2365

Addressing comment on PR #2365 #16

Triggered via dynamic January 28, 2026 21:40
Status Success
Total duration 33m 47s
Artifacts

copilot

on: dynamic
Fit to window
Zoom out
Zoom in

Annotations

10 errors
tests/rule-setup.js > rules setup is correct > rule files > template-no-unused-block-params > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Disallow unused…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Disallow unused block params + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unused block parameters in templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unused-block-params.md', + }, + schema: [], + messages: { + unusedBlockParam: 'Block param "{{param}}" is unused', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + const blockParams = node.program?.blockParams || []; + if (blockParams.length === 0) { + return; + } + + const usedParams = new Set(); + + function checkNode(n) { + if (!n) { + return; + } + + if (n.type === 'PathExpression') { + const firstPart = n.original.split('.')[0]; + if (blockParams.includes(firstPart)) { + usedParams.add(firstPart); + } + } + + // Recursively check children + if (n.program) { + checkNode(n.program); + } + if (n.inverse) { + checkNode(n.inverse); + } + if (n.params) { + for (const param of n.params) { + checkNode(param); + } + } + if (n.hash && n.hash.pairs) { + for (const pair of n.hash.pairs) { + checkNode(pair.value); + } + } + if (n.body) { + for (const bodyNode of n.body) { + checkNode(bodyNode); + } + } + if (n.path) { + checkNode(n.path); + } + if (n.attributes) { + for (const attr of n.attributes) { + checkNode(attr.value); + } + } + if (n.children) { + for (const child of n.children) { + checkNode(child); + } + } + } + + checkNode(node.program); + + // Report unused params + for (const param of blockParams) { + if (!usedParams.has(param)) { + context.report({ + node, + messageId: 'unusedBlockParam', + data: { param }, + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-unnecessary-component-helper > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Disallow unnece…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Disallow unnecessary usage of (component) helper + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unnecessary component helper', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-component-helper.md', + }, + schema: [], + messages: { + noUnnecessaryComponent: + 'Unnecessary use of (component) helper. Use angle bracket invocation instead.', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path && + node.path.type === 'PathExpression' && + node.path.original === 'component' && + node.params && + node.params.length > 0 && + node.params[0].type === 'StringLiteral' + ) { + context.report({ + node, + messageId: 'noUnnecessaryComponent', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-implicit-this > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Require explici…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Require explicit this for property access in templates + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require explicit `this` in property access', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-implicit-this.md', + }, + schema: [], + messages: { + noImplicitThis: + 'Ambiguous path "{{path}}" is not allowed. Use "@{{path}}" if it is a named argument or "this.{{path}}" if it is a property on the component.', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerPathExpression(node) { + const path = node.original; + + // Skip if path starts with @ (named arg) or this. (explicit) + if (path.startsWith('@') || path.startsWith('this.')) { + return; + } + + // Skip built-in helpers and keywords + const builtIns = [ + 'yield', + 'outlet', + 'has-block', + 'has-block-params', + 'if', + 'unless', + 'each', + 'let', + 'with', + 'each-in', + 'concat', + 'get', + 'array', + 'hash', + 'log', + 'debugger', + 'component', + 'helper', + 'modifier', + 'mount', + ]; + if (builtIns.includes(path)) { + return; + } + + // Skip if it's a helper with a dash (likely a helper call) + if (path.includes('-')) { + return; + } + + // Skip single identifiers that look like helpers in MustacheStatement + if (node.parent && node.parent.type === 'GlimmerMustacheStatement') { + // If it's the path of a mustache with params, it's likely a helper + if (node.parent.params && node.parent.params.length > 0) { + return; + } + // If it has hash pairs, it's likely a helper + if (node.parent.hash && node.parent.hash.pairs && node.parent.hash.pairs.length > 0) { + return; + } + } + + // Skip paths that are part of block params + if (node.parent && node.parent.type === 'GlimmerBlockStatement') { + const blockParams = node.parent.program?.blockParams || []; + if (blockParams.includes(path.split('.')[0])) { + return; + } + } + + // Report ambiguous paths that should use this. or @ + if (!path.includes('.') || !path.startsWith('this.')) { + const firstPart = path.split('.')[0]; + + // Skip if it looks like a component (PascalCase) + if (firstPart[0] === firstPart[0].toUpperCase()) { + return; + } + + context.report({ + node, + messageId: 'noImplicitThis', + data: { path }, + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-curly-component-invocation > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Disallow curly …' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Disallow curly component invocation + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow curly component invocation', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-curly-component-invocation.md', + }, + schema: [], + messages: { + noCurlyInvocation: 'Use angle bracket component invocation instead of curly: <{{name}} />', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if (node.path && node.path.type === 'PathExpression') { + const pathName = node.path.original; + + // Check if this is a component invocation (starts with uppercase or has a dash) + if (pathName && (pathName[0] === pathName[0].toUpperCase() || pathName.includes('-'))) { + // Exclude common helpers that might match the pattern + const helpers = ['Input', 'Textarea', 'LinkTo']; + if (!helpers.includes(pathName)) { + context.report({ + node, + messageId: 'noCurlyInvocation', + data: { name: pathName }, + }); + } + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-require-input-label > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Require label f…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Require label for form input elements + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require label for form input elements', + category: 'Accessibility', + strictGjs: true, + strictGts: true, + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-input-label.md', + }, + schema: [], + messages: { + requireLabel: 'Input elements should have an associated label', + }, + }, + + create(context) { + return { + GlimmerElementNode(node) { + const tagName = node.tag.toLowerCase(); + if (tagName !== 'input' && tagName !== 'textarea' && tagName !== 'select') { + return; + } + + // Skip if input has type="hidden" + const typeAttr = node.attributes.find( + (attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'type' + ); + if (typeAttr && typeAttr.value) { + // Check if value is GlimmerTextNode with "hidden" + if (typeAttr.value.type === 'GlimmerTextNode' && typeAttr.value.chars === 'hidden') { + return; + } + // Check if value is ConcatStatement (for dynamic values) - skip those + if (typeAttr.value.type === 'ConcatStatement') { + return; + } + } + + // Check for id attribute + const hasId = node.attributes.some( + (attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'id' + ); + + // Check for aria-label or aria-labelledby + const hasAriaLabel = node.attributes.some( + (attr) => + attr.type === 'GlimmerAttrNode' && + (attr.name === 'aria-label' || attr.name === 'aria-labelledby') + ); + + if (!hasId && !hasAriaLabel) { + context.report({ + node, + messageId: 'requireLabel', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-whitespace-within-word > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Disallow whites…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Disallow whitespace within mustache or block expression + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'layout', + docs: { + description: 'disallow whitespace within mustache or block expressions', + category: 'Style', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-whitespace-within-word.md', + }, + schema: [], + messages: { + noWhitespace: 'Unexpected whitespace in mustache expression', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + return { + 'GlimmerMustacheStatement, GlimmerBlockStatement'(node) { + const text = sourceCode.getText(node); + + // Check for {{ foo }} pattern (space after opening or before closing) + if (/^{{\s/.test(text) || /\s}}$/.test(text)) { + context.report({ + node, + messageId: 'noWhitespace', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-unused-block-params > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Disallow unused…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Disallow unused block params + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unused block parameters in templates', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unused-block-params.md', + }, + schema: [], + messages: { + unusedBlockParam: 'Block param "{{param}}" is unused', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerBlockStatement(node) { + const blockParams = node.program?.blockParams || []; + if (blockParams.length === 0) { + return; + } + + const usedParams = new Set(); + + function checkNode(n) { + if (!n) { + return; + } + + if (n.type === 'PathExpression') { + const firstPart = n.original.split('.')[0]; + if (blockParams.includes(firstPart)) { + usedParams.add(firstPart); + } + } + + // Recursively check children + if (n.program) { + checkNode(n.program); + } + if (n.inverse) { + checkNode(n.inverse); + } + if (n.params) { + for (const param of n.params) { + checkNode(param); + } + } + if (n.hash && n.hash.pairs) { + for (const pair of n.hash.pairs) { + checkNode(pair.value); + } + } + if (n.body) { + for (const bodyNode of n.body) { + checkNode(bodyNode); + } + } + if (n.path) { + checkNode(n.path); + } + if (n.attributes) { + for (const attr of n.attributes) { + checkNode(attr.value); + } + } + if (n.children) { + for (const child of n.children) { + checkNode(child); + } + } + } + + checkNode(node.program); + + // Report unused params + for (const param of blockParams) { + if (!usedParams.has(param)) { + context.report({ + node, + messageId: 'unusedBlockParam', + data: { param }, + }); + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-unnecessary-component-helper > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Disallow unnece…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Disallow unnecessary usage of (component) helper + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unnecessary component helper', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-component-helper.md', + }, + schema: [], + messages: { + noUnnecessaryComponent: + 'Unnecessary use of (component) helper. Use angle bracket invocation instead.', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if ( + node.path && + node.path.type === 'PathExpression' && + node.path.original === 'component' && + node.params && + node.params.length > 0 && + node.params[0].type === 'StringLiteral' + ) { + context.report({ + node, + messageId: 'noUnnecessaryComponent', + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-implicit-this > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Require explici…' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Require explicit this for property access in templates + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require explicit `this` in property access', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-implicit-this.md', + }, + schema: [], + messages: { + noImplicitThis: + 'Ambiguous path "{{path}}" is not allowed. Use "@{{path}}" if it is a named argument or "this.{{path}}" if it is a property on the component.', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerPathExpression(node) { + const path = node.original; + + // Skip if path starts with @ (named arg) or this. (explicit) + if (path.startsWith('@') || path.startsWith('this.')) { + return; + } + + // Skip built-in helpers and keywords + const builtIns = [ + 'yield', + 'outlet', + 'has-block', + 'has-block-params', + 'if', + 'unless', + 'each', + 'let', + 'with', + 'each-in', + 'concat', + 'get', + 'array', + 'hash', + 'log', + 'debugger', + 'component', + 'helper', + 'modifier', + 'mount', + ]; + if (builtIns.includes(path)) { + return; + } + + // Skip if it's a helper with a dash (likely a helper call) + if (path.includes('-')) { + return; + } + + // Skip single identifiers that look like helpers in MustacheStatement + if (node.parent && node.parent.type === 'GlimmerMustacheStatement') { + // If it's the path of a mustache with params, it's likely a helper + if (node.parent.params && node.parent.params.length > 0) { + return; + } + // If it has hash pairs, it's likely a helper + if (node.parent.hash && node.parent.hash.pairs && node.parent.hash.pairs.length > 0) { + return; + } + } + + // Skip paths that are part of block params + if (node.parent && node.parent.type === 'GlimmerBlockStatement') { + const blockParams = node.parent.program?.blockParams || []; + if (blockParams.includes(path.split('.')[0])) { + return; + } + } + + // Report ambiguous paths that should use this. or @ + if (!path.includes('.') || !path.startsWith('this.')) { + const firstPart = path.split('.')[0]; + + // Skip if it looks like a component (PascalCase) + if (firstPart[0] === firstPart[0].toUpperCase()) { + return; + } + + context.report({ + node, + messageId: 'noImplicitThis', + data: { path }, + }); + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24
tests/rule-setup.js > rules setup is correct > rule files > template-no-curly-component-invocation > should have the jsdoc comment for rule type: tests/rule-setup.js#L46
AssertionError: expected '/**\n * @fileoverview Disallow curly …' to contain '/** @type {import(\'eslint\').Rule.Ru…' - Expected + Received - /** @type {import('eslint').Rule.RuleModule} */ + /** + * @fileoverview Disallow curly component invocation + * @type {import('eslint').Rule.RuleModule} + */ + + module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow curly component invocation', + category: 'Best Practices', + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-curly-component-invocation.md', + }, + schema: [], + messages: { + noCurlyInvocation: 'Use angle bracket component invocation instead of curly: <{{name}} />', + }, + strictGjs: true, + strictGts: true, + }, + + create(context) { + return { + GlimmerMustacheStatement(node) { + if (node.path && node.path.type === 'PathExpression') { + const pathName = node.path.original; + + // Check if this is a component invocation (starts with uppercase or has a dash) + if (pathName && (pathName[0] === pathName[0].toUpperCase() || pathName.includes('-'))) { + // Exclude common helpers that might match the pattern + const helpers = ['Input', 'Textarea', 'LinkTo']; + if (!helpers.includes(pathName)) { + context.report({ + node, + messageId: 'noCurlyInvocation', + data: { name: pathName }, + }); + } + } + } + }, + }; + }, + }; + ❯ tests/rule-setup.js:46:24