ember-template-lint
Version:
Linter for Ember or Handlebars templates.
101 lines (82 loc) • 3.08 kB
JavaScript
;
const AstNodeInfo = require('../helpers/ast-node-info');
const Rule = require('./_base');
const ERROR_MESSAGE_NO_LABEL = 'form elements require a valid associated label.';
const ERROR_MESSAGE_MULTIPLE_LABEL = 'form elements should not have multiple labels.';
function hasValidLabelParent(path) {
// Parental validation (descriptive elements)
let parents = [...path.parents()];
let labelParentPath = parents.find(
(parent) => parent.node.type === 'ElementNode' && parent.node.tag === 'label'
);
if (labelParentPath && AstNodeInfo.childrenFor(labelParentPath.node).length > 1) {
return true;
}
return false;
}
const INCLUDED_TAGS = new Set(['Input', 'input', 'Textarea', 'textarea', 'select']);
const INCLUDED_COMPONENTS = new Set(['input', 'textarea']);
module.exports = class RequireInputLabel extends Rule {
visitor() {
return {
ElementNode(node, path) {
// Only input elements: check rule conditions
if (!INCLUDED_TAGS.has(node.tag)) {
return;
}
if (AstNodeInfo.hasAttribute(node, '...attributes')) {
return;
}
let labelCount = 0;
if (hasValidLabelParent(path)) {
labelCount++;
}
const typeAttribute = AstNodeInfo.findAttribute(node, 'type');
if (typeAttribute && typeAttribute.value.chars === 'hidden') {
return;
}
// An input can be validated by either:
// Self-validation (descriptive attributes)
let validAttributesList = ['id', 'aria-label', 'aria-labelledby'];
let attributes = validAttributesList.filter((name) => AstNodeInfo.hasAttribute(node, name));
labelCount += attributes.length;
if (labelCount === 1) {
return;
}
if (hasValidLabelParent(path) && AstNodeInfo.hasAttribute(node, 'id')) {
return;
}
let message = labelCount === 0 ? ERROR_MESSAGE_NO_LABEL : ERROR_MESSAGE_MULTIPLE_LABEL;
this.log({
message,
line: node.loc && node.loc.start.line,
column: node.loc && node.loc.start.column,
source: this.sourceForNode(node),
});
},
MustacheStatement(node, path) {
if (node.path.type !== 'PathExpression' || !INCLUDED_COMPONENTS.has(node.path.original)) {
return;
}
if (hasValidLabelParent(path)) {
return;
}
const typeAttribute = node.hash.pairs.find((pair) => pair.key === 'type');
if (typeAttribute && typeAttribute.value.value === 'hidden') {
return;
}
if (node.hash.pairs.some((pair) => pair.key === 'id')) {
return;
}
this.log({
message: ERROR_MESSAGE_NO_LABEL,
line: node.loc && node.loc.start.line,
column: node.loc && node.loc.start.column,
source: this.sourceForNode(node),
});
},
};
}
};
module.exports.ERROR_MESSAGE = ERROR_MESSAGE_NO_LABEL;
module.exports.ERROR_MESSAGE_MULTIPLE_LABEL = ERROR_MESSAGE_MULTIPLE_LABEL;