UNPKG

eslint-plugin-ember

Version:
92 lines (81 loc) 2.87 kB
function hasParentForm(node) { let current = node.parent; while (current) { if (current.type === 'GlimmerElementNode' && current.tag === 'form') { return true; } current = current.parent; } return false; } /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'problem', docs: { description: 'require button elements to have a valid type attribute', category: 'Best Practices', url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-button-type.md', templateMode: 'both', }, fixable: 'code', schema: [], messages: { missing: 'All `<button>` elements should have a valid `type` attribute', invalid: 'All `<button>` elements should have a valid `type` attribute', }, originallyFrom: { name: 'ember-template-lint', rule: 'lib/rules/require-button-type.js', docs: 'docs/rule/require-button-type.md', tests: 'test/unit/rules/require-button-type-test.js', }, }, create(context) { const sourceCode = context.sourceCode; return { GlimmerElementNode(node) { if (node.tag !== 'button') { return; } const typeAttr = node.attributes?.find((attr) => attr.name === 'type'); if (!typeAttr) { context.report({ node, messageId: 'missing', fix(fixer) { // If inside a form, default to "submit", otherwise "button" const defaultType = hasParentForm(node) ? 'submit' : 'button'; const text = sourceCode.getText(node); // Handle self-closing: <button/> or <button /> if (/^<button\s*\/>/.test(text)) { return fixer.replaceText(node, `<button type="${defaultType}" />`); } // Find the position to insert the attribute const openTag = text.match(/^<button[^>]*/)[0]; const insertPos = node.range[0] + openTag.length; return fixer.insertTextBeforeRange([insertPos, insertPos], ` type="${defaultType}"`); }, }); return; } // Check if the type value is valid const value = typeAttr.value; if (value && value.type === 'GlimmerTextNode') { const typeValue = value.chars; if (!['button', 'submit', 'reset'].includes(typeValue)) { context.report({ node: typeAttr, messageId: 'invalid', fix(fixer) { return fixer.replaceText(typeAttr, 'type="button"'); }, }); } } // Note: Dynamic type values (e.g., type={{this.dynamicType}}) cannot be // validated at lint time, so we don't report them as errors }, }; }, };