Skip to content

Commit 475aaca

Browse files
thedaviddiasSqrTT
andcommittedMay 18, 2020
feat: Add tags checking rule - allows specify rules for any tag and validate 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>
1 parent 60867c3 commit 475aaca

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed
 

‎src/rules/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ export { default as tagSelfClose } from './tag-self-close';
2626
export { default as tagnameLowercase } from './tagname-lowercase';
2727
export { default as tagnameSpecialChars } from './tagname-specialchars';
2828
export { default as titleRequire } from './title-require';
29+
export { default as tagsCheck } from './tags-check';

‎src/rules/tags-check.js

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
2+
var tagsTypings = {
3+
a: {
4+
selfclosing: false,
5+
attrsRequired: ['href', 'title'],
6+
redundantAttrs: ['alt']
7+
},
8+
div: {
9+
selfclosing: false
10+
},
11+
main: {
12+
selfclosing: false,
13+
redundantAttrs: ['role']
14+
},
15+
nav: {
16+
selfclosing: false,
17+
redundantAttrs: ['role']
18+
},
19+
script: {
20+
attrsOptional: [['async', 'async'], ['defer', 'defer']]
21+
},
22+
img: {
23+
selfclosing: true,
24+
attrsRequired: [
25+
'src', 'alt', 'title'
26+
]
27+
}
28+
};
29+
30+
var assign = function(target) {
31+
var _source;
32+
33+
for (var i = 1; i < arguments.length; i++) {
34+
_source = arguments[i];
35+
for (var prop in _source) {
36+
target[prop] = _source[prop];
37+
}
38+
}
39+
return target;
40+
}
41+
42+
export default {
43+
id: 'tags-check',
44+
description: 'Checks html tags.',
45+
init: function (parser, reporter, options) {
46+
var self = this;
47+
48+
if (typeof options !== 'boolean') {
49+
assign(tagsTypings, options);
50+
}
51+
52+
parser.addListener('tagstart', function (event) {
53+
var attrs = event.attrs;
54+
var col = event.col + event.tagName.length + 1;
55+
56+
var tagName = event.tagName.toLowerCase();
57+
58+
if (tagsTypings[tagName]) {
59+
var currentTagType = tagsTypings[tagName];
60+
61+
if (currentTagType.selfclosing === true && !event.close) {
62+
reporter.warn('The <' + tagName + '> tag must be selfclosing.', event.line, event.col, self, event.raw);
63+
} else if (currentTagType.selfclosing === false && event.close) {
64+
reporter.warn('The <' + tagName +'> tag must not be selfclosing.', event.line, event.col, self, event.raw);
65+
}
66+
67+
if (currentTagType.attrsRequired) {
68+
currentTagType.attrsRequired.forEach(function (id) {
69+
if (Array.isArray(id)) {
70+
var copyOfId = id.map(function (a) { return a;});
71+
var realID = copyOfId.shift();
72+
var values = copyOfId;
73+
74+
if (attrs.some(function (attr) {return attr.name === realID;})) {
75+
attrs.forEach(function (attr) {
76+
if (attr.name === realID && values.indexOf(attr.value) === -1) {
77+
reporter.error('The <' + tagName +'> tag must have attr \'' + realID + '\' with one value of \'' + values.join('\' or \'') + '\'.', event.line, col, self, event.raw);
78+
}
79+
});
80+
} else {
81+
reporter.error('The <' + tagName + '> tag must have attr \'' + realID + '\'.', event.line, col, self, event.raw);
82+
}
83+
} else if (!attrs.some(function (attr) {return id.split('|').indexOf(attr.name) !== -1;})) {
84+
reporter.error('The <' + tagName + '> tag must have attr \'' + id + '\'.', event.line, col, self, event.raw);
85+
}
86+
});
87+
}
88+
if (currentTagType.attrsOptional) {
89+
currentTagType.attrsOptional.forEach(function (id) {
90+
if (Array.isArray(id)) {
91+
var copyOfId = id.map(function (a) { return a;});
92+
var realID = copyOfId.shift();
93+
var values = copyOfId;
94+
95+
if (attrs.some(function (attr) {return attr.name === realID;})) {
96+
attrs.forEach(function (attr) {
97+
if (attr.name === realID && values.indexOf(attr.value) === -1) {
98+
reporter.error('The <' + tagName + '> tag must have optional attr \'' + realID +
99+
'\' with one value of \'' + values.join('\' or \'') + '\'.', event.line, col, self, event.raw);
100+
}
101+
});
102+
}
103+
}
104+
});
105+
}
106+
107+
if (currentTagType.redundantAttrs) {
108+
currentTagType.redundantAttrs.forEach(function (attrName) {
109+
if (attrs.some(function (attr) { return attr.name === attrName;})) {
110+
reporter.error('The attr \'' + attrName + '\' is redundant for <' + tagName + '> and should be ommited.', event.line, col, self, event.raw);
111+
}
112+
});
113+
}
114+
115+
}
116+
});
117+
}
118+
};

‎test/rules/tags-check.spec.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
const expect = require("expect.js");
3+
4+
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint;
5+
6+
const ruldId = 'tags-check',
7+
ruleOptions = {};
8+
9+
ruleOptions[ruldId] = {
10+
sometag: {
11+
selfclosing: true,
12+
attrsRequired: [['attrname', 'attrvalue']]
13+
}
14+
};
15+
16+
describe('Rules: ' + ruldId, function(){
17+
it('Tag <a> should have requered attrs [title, href]', function(){
18+
var code = '<a>blabla</a>';
19+
var messages = HTMLHint.verify(code, ruleOptions);
20+
expect(messages.length).to.be(2);
21+
expect(messages[0].rule.id).to.be(ruldId);
22+
expect(messages[1].rule.id).to.be(ruldId);
23+
});
24+
it('Tag <a> should not be selfclosing', function(){
25+
var code = '<a href="bbb" title="aaa"/>';
26+
var messages = HTMLHint.verify(code, ruleOptions);
27+
expect(messages.length).to.be(1);
28+
expect(messages[0].rule.id).to.be(ruldId);
29+
});
30+
it('Tag <img> should be selfclosing', function(){
31+
var code = '<img src="bbb" title="aaa" alt="asd"></img>';
32+
var messages = HTMLHint.verify(code, ruleOptions);
33+
expect(messages.length).to.be(1);
34+
expect(messages[0].rule.id).to.be(ruldId);
35+
});
36+
it('Should check optional attributes', function(){
37+
var code = '<script src="aaa" async="sad" />';
38+
var messages = HTMLHint.verify(code, ruleOptions);
39+
expect(messages.length).to.be(1);
40+
expect(messages[0].rule.id).to.be(ruldId);
41+
});
42+
it('Should check redunant attributes', function(){
43+
var code = '<main role="main" />';
44+
var messages = HTMLHint.verify(code, ruleOptions);
45+
expect(messages.length).to.be(2);
46+
expect(messages[0].rule.id).to.be(ruldId);
47+
expect(messages[1].rule.id).to.be(ruldId);
48+
});
49+
it('Should be extendable trought config', function(){
50+
var code = '<sometag></sometag>';
51+
var messages = HTMLHint.verify(code, ruleOptions);
52+
expect(messages.length).to.be(2);
53+
expect(messages[0].rule.id).to.be(ruldId);
54+
});
55+
it('Should check required attributes with specifyed values', function(){
56+
var code = '<sometag attrname="attrvalue" />';
57+
var messages = HTMLHint.verify(code, ruleOptions);
58+
expect(messages.length).to.be(0);
59+
code = '<sometag attrname="wrong_value" />';
60+
messages = HTMLHint.verify(code, ruleOptions);
61+
expect(messages.length).to.be(1);
62+
});
63+
});

0 commit comments

Comments
 (0)
Please sign in to comment.