Skip to content

Commit

Permalink
feat: Add tags checking rule - allows specify rules for any tag and v…
Browse files Browse the repository at this point in the history
…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
thedaviddias and SqrTT committed May 18, 2020
1 parent 60867c3 commit 475aaca
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export { default as tagSelfClose } from './tag-self-close';
export { default as tagnameLowercase } from './tagname-lowercase';
export { default as tagnameSpecialChars } from './tagname-specialchars';
export { default as titleRequire } from './title-require';
export { default as tagsCheck } from './tags-check';
118 changes: 118 additions & 0 deletions src/rules/tags-check.js
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);
}
});
}

}
});
}
};
63 changes: 63 additions & 0 deletions test/rules/tags-check.spec.js
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);
});
});

0 comments on commit 475aaca

Please sign in to comment.