Addressing comment on PR #2365 #16
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
|