-
-
Notifications
You must be signed in to change notification settings - Fork 378
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add tags checking rule - allows specify rules for any tag and v…
…alidate that (#384) * adding tags check rule * fix missing commas * add polifil for old JS engines * add polifil for old JS engines * fix missing commas * fix indexOf * incrace code covarage * incrace code covarage * review fix * fix formating * fixing issues Co-authored-by: a.obitskyi <a.obitskyi@astoundcommerce.com>
- Loading branch information
1 parent
60867c3
commit c0be01d
Showing
3 changed files
with
182 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
|
||
var tagsTypings = { | ||
a: { | ||
selfclosing: false, | ||
attrsRequired: ['href', 'title'], | ||
redundantAttrs: ['alt'] | ||
}, | ||
div: { | ||
selfclosing: false | ||
}, | ||
main: { | ||
selfclosing: false, | ||
redundantAttrs: ['role'] | ||
}, | ||
nav: { | ||
selfclosing: false, | ||
redundantAttrs: ['role'] | ||
}, | ||
script: { | ||
attrsOptional: [['async', 'async'], ['defer', 'defer']] | ||
}, | ||
img: { | ||
selfclosing: true, | ||
attrsRequired: [ | ||
'src', 'alt', 'title' | ||
] | ||
} | ||
}; | ||
|
||
var assign = function(target) { | ||
var _source; | ||
|
||
for (var i = 1; i < arguments.length; i++) { | ||
_source = arguments[i]; | ||
for (var prop in _source) { | ||
target[prop] = _source[prop]; | ||
} | ||
} | ||
return target; | ||
} | ||
|
||
export default { | ||
id: 'tags-check', | ||
description: 'Checks html tags.', | ||
init: function (parser, reporter, options) { | ||
var self = this; | ||
|
||
if (typeof options !== 'boolean') { | ||
assign(tagsTypings, options); | ||
} | ||
|
||
parser.addListener('tagstart', function (event) { | ||
var attrs = event.attrs; | ||
var col = event.col + event.tagName.length + 1; | ||
|
||
var tagName = event.tagName.toLowerCase(); | ||
|
||
if (tagsTypings[tagName]) { | ||
var currentTagType = tagsTypings[tagName]; | ||
|
||
if (currentTagType.selfclosing === true && !event.close) { | ||
reporter.warn('The <' + tagName + '> tag must be selfclosing.', event.line, event.col, self, event.raw); | ||
} else if (currentTagType.selfclosing === false && event.close) { | ||
reporter.warn('The <' + tagName +'> tag must not be selfclosing.', event.line, event.col, self, event.raw); | ||
} | ||
|
||
if (currentTagType.attrsRequired) { | ||
currentTagType.attrsRequired.forEach(function (id) { | ||
if (Array.isArray(id)) { | ||
var copyOfId = id.map(function (a) { return a;}); | ||
var realID = copyOfId.shift(); | ||
var values = copyOfId; | ||
|
||
if (attrs.some(function (attr) {return attr.name === realID;})) { | ||
attrs.forEach(function (attr) { | ||
if (attr.name === realID && values.indexOf(attr.value) === -1) { | ||
reporter.error('The <' + tagName +'> tag must have attr \'' + realID + '\' with one value of \'' + values.join('\' or \'') + '\'.', event.line, col, self, event.raw); | ||
} | ||
}); | ||
} else { | ||
reporter.error('The <' + tagName + '> tag must have attr \'' + realID + '\'.', event.line, col, self, event.raw); | ||
} | ||
} else if (!attrs.some(function (attr) {return id.split('|').indexOf(attr.name) !== -1;})) { | ||
reporter.error('The <' + tagName + '> tag must have attr \'' + id + '\'.', event.line, col, self, event.raw); | ||
} | ||
}); | ||
} | ||
if (currentTagType.attrsOptional) { | ||
currentTagType.attrsOptional.forEach(function (id) { | ||
if (Array.isArray(id)) { | ||
var copyOfId = id.map(function (a) { return a;}); | ||
var realID = copyOfId.shift(); | ||
var values = copyOfId; | ||
|
||
if (attrs.some(function (attr) {return attr.name === realID;})) { | ||
attrs.forEach(function (attr) { | ||
if (attr.name === realID && values.indexOf(attr.value) === -1) { | ||
reporter.error('The <' + tagName + '> tag must have optional attr \'' + realID + | ||
'\' with one value of \'' + values.join('\' or \'') + '\'.', event.line, col, self, event.raw); | ||
} | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
if (currentTagType.redundantAttrs) { | ||
currentTagType.redundantAttrs.forEach(function (attrName) { | ||
if (attrs.some(function (attr) { return attr.name === attrName;})) { | ||
reporter.error('The attr \'' + attrName + '\' is redundant for <' + tagName + '> and should be ommited.', event.line, col, self, event.raw); | ||
} | ||
}); | ||
} | ||
|
||
} | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
|
||
const expect = require("expect.js"); | ||
|
||
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint; | ||
|
||
const ruldId = 'tags-check', | ||
ruleOptions = {}; | ||
|
||
ruleOptions[ruldId] = { | ||
sometag: { | ||
selfclosing: true, | ||
attrsRequired: [['attrname', 'attrvalue']] | ||
} | ||
}; | ||
|
||
describe('Rules: ' + ruldId, function(){ | ||
it('Tag <a> should have requered attrs [title, href]', function(){ | ||
var code = '<a>blabla</a>'; | ||
var messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(2); | ||
expect(messages[0].rule.id).to.be(ruldId); | ||
expect(messages[1].rule.id).to.be(ruldId); | ||
}); | ||
it('Tag <a> should not be selfclosing', function(){ | ||
var code = '<a href="bbb" title="aaa"/>'; | ||
var messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(1); | ||
expect(messages[0].rule.id).to.be(ruldId); | ||
}); | ||
it('Tag <img> should be selfclosing', function(){ | ||
var code = '<img src="bbb" title="aaa" alt="asd"></img>'; | ||
var messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(1); | ||
expect(messages[0].rule.id).to.be(ruldId); | ||
}); | ||
it('Should check optional attributes', function(){ | ||
var code = '<script src="aaa" async="sad" />'; | ||
var messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(1); | ||
expect(messages[0].rule.id).to.be(ruldId); | ||
}); | ||
it('Should check redunant attributes', function(){ | ||
var code = '<main role="main" />'; | ||
var messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(2); | ||
expect(messages[0].rule.id).to.be(ruldId); | ||
expect(messages[1].rule.id).to.be(ruldId); | ||
}); | ||
it('Should be extendable trought config', function(){ | ||
var code = '<sometag></sometag>'; | ||
var messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(2); | ||
expect(messages[0].rule.id).to.be(ruldId); | ||
}); | ||
it('Should check required attributes with specifyed values', function(){ | ||
var code = '<sometag attrname="attrvalue" />'; | ||
var messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(0); | ||
code = '<sometag attrname="wrong_value" />'; | ||
messages = HTMLHint.verify(code, ruleOptions); | ||
expect(messages.length).to.be(1); | ||
}); | ||
}); |