Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add tags checking rule - allows specify rules for any tag and validate that #384

Merged
merged 11 commits into from
May 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should convert all of this functions to arrow-functions
but we should do this for the whole codebase and use eslint to find all of this occurrences

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the plan right after we merge all old PRs :)

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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this file singlequotes seems to be used
we could format the whole codebase with prettier later, so this wont be an issue for now


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);
});
});