eslint-plugin-ember
Version:
ESLint plugin for Ember.js apps
92 lines (81 loc) • 2.87 kB
JavaScript
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
},
};
},
};