diff --git a/src/rules/index.js b/src/rules/index.js index c924ed29e..005f1dc94 100644 --- a/src/rules/index.js +++ b/src/rules/index.js @@ -16,6 +16,7 @@ export { default as idClassValue } from './id-class-value'; export { default as idUnique } from './id-unique'; export { default as inlineScriptDisabled } from './inline-script-disabled'; export { default as inlineStyleDisabled } from './inline-style-disabled'; +export { default as inputRequiresLabel } from './input-requires-label'; export { default as scriptDisabled } from './script-disabled'; export { default as spaceTabMixedDisabled } from './space-tab-mixed-disabled'; export { default as specCharEscape } from './spec-char-escape'; diff --git a/src/rules/input-requires-label.js b/src/rules/input-requires-label.js new file mode 100644 index 000000000..aae039b7e --- /dev/null +++ b/src/rules/input-requires-label.js @@ -0,0 +1,46 @@ +export default { + id: 'input-requires-label', + description: 'All [ input ] tags must have a corresponding [ label ] tag. ', + init: function(parser, reporter){ + var self = this, + labelTags = [], + inputTags = []; + + parser.addListener('tagstart', function(event) { + var tagName = event.tagName.toLowerCase(), + mapAttrs = parser.getMapAttrs(event.attrs), + col = event.col + tagName.length + 1; + + if (tagName === 'input') { + inputTags.push({event: event, col: col, id: mapAttrs['id']}); + } + + if (tagName === 'label') { + if (('for' in mapAttrs) && mapAttrs['for'] !== '') { + labelTags.push({event: event, col: col, forValue: mapAttrs['for']}); + } + } + + }); + + parser.addListener('end', function() { + inputTags.forEach(function(inputTag) { + if (!hasMatchingLabelTag(inputTag)) { + reporter.warn('No matching [ label ] tag found.', inputTag.event.line, inputTag.col, self, inputTag.event.raw); + } + }); + }); + + + function hasMatchingLabelTag(inputTag) { + var found = false; + labelTags.forEach(function(labelTag){ + if (inputTag.id && (inputTag.id === labelTag.forValue)) { + found = true; + } + }); + return found; + + } + } +}; diff --git a/test/rules/input-requires-label.spec.js b/test/rules/input-requires-label.spec.js new file mode 100644 index 000000000..700786683 --- /dev/null +++ b/test/rules/input-requires-label.spec.js @@ -0,0 +1,71 @@ +const expect = require("expect.js"); + +const HTMLHint = require('../../dist/htmlhint.js').HTMLHint; + +const ruleId = 'input-requires-label'; +const ruleOptions = {}; + +ruleOptions[ruleId] = true; + +describe(`Rules: ${ruleId}`, function(){ + + describe('Successful cases', function() { + + it('Input tag with a matching label before should result in no error', function () { + var code = '