diff --git a/LICENSE.md b/LICENSE.md index 9dba9f386..2e1fd2890 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,7 +2,7 @@ The MIT License ================ -Copyright (c) 2012 Yanis Wang \ +Copyright (c) 2013 Yanis Wang \ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/coverage.html b/coverage.html index 04c813852..a0b10c3e6 100644 --- a/coverage.html +++ b/coverage.html @@ -338,4 +338,4 @@ code .string { color: #5890AD } code .keyword { color: #8A6343 } code .number { color: #2F6FAD } -

Coverage

54%
267
146
121

htmlhint.js

54%
267
146
121
LineHitsSource
1/**
2 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
3 * MIT Licensed
4 */
51var HTMLHint = (function (undefined) {
6
71 var rules = [];
8
91 var HTMLHint = {};
10
111 HTMLHint.version = '@VERSION';
12
131 HTMLHint.addRule = function(rule){
1413 rules[rule.id] = rule;
15 };
16
171 HTMLHint.verify = function(html, ruleset){
185 var parser = new HTMLParser(),
19 reporter = new HTMLHint.Reporter(html.split(/\r?\n/), ruleset);
20
215 var rule;
225 for (var id in ruleset){
235 rule = rules[id];
245 if (rule !== undefined){
255 rule.init(parser, reporter, ruleset[id]);
26 }
27 }
28
295 parser.parse(html);
30
315 return reporter.messages;
32 };
33
341 return HTMLHint;
35
36})();
37
381if (typeof exports === 'object' && exports){
391 exports.HTMLHint = HTMLHint;
40}
41/**
42 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
43 * MIT Licensed
44 */
451(function(HTMLHint, undefined){
46
471 var Reporter = function(){
485 var self = this;
495 self._init.apply(self,arguments);
50 };
51
521 Reporter.prototype = {
53 _init: function(lines, ruleset){
545 var self = this;
555 self.lines = lines;
565 self.ruleset = ruleset;
575 self.messages = [];
58 },
59 //错误
60 error: function(message, line, col, rule, raw){
612 this.report('error', message, line, col, rule, raw);
62 },
63 //警告
64 warn: function(message, line, col, rule, raw){
650 this.report('warning', message, line, col, rule, raw);
66 },
67 //信息
68 info: function(message, line, col, rule, raw){
690 this.report('info', message, line, col, rule, raw);
70 },
71 //报告
72 report: function(type, message, line, col, rule, raw){
732 var self = this;
742 self.messages.push({
75 type: type,
76 message: message,
77 raw: raw,
78 evidence: self.lines[line-1],
79 line: line,
80 col: col,
81 rule: rule
82 });
83 }
84 };
85
861 HTMLHint.Reporter = Reporter;
87
88})(HTMLHint);
89/**
90 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
91 * MIT Licensed
92 */
931var HTMLParser = (function(undefined){
94
951 var HTMLParser = function(){
9624 var self = this;
9724 self._init.apply(self,arguments);
98 };
99
1001 HTMLParser.prototype = {
101 _init: function(){
10224 var self = this;
10324 self._listeners = {};
10424 self._mapCdataTags = self.makeMap("script,style");
10524 self._arrBlocks = [];
106 },
107
108 makeMap: function(str){
10924 var obj = {}, items = str.split(",");
11024 for ( var i = 0; i < items.length; i++ ){
11148 obj[ items[i] ] = true;
112 }
11324 return obj;
114 },
115
116 parse: function(html){
117
11824 var self = this,
119 mapCdataTags = self._mapCdataTags;
120
12124 var regTag=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:"[^"]*"|'[^']*'|[^"'<>])*?)\s*(\/?))>/g,
122 regAttr = /\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s]+)))?/g,
123 regLine = /\r?\n/g;
124
12524 var match, matchIndex, lastIndex = 0, tagName, arrAttrs, tagCDATA, arrCDATA, lastCDATAIndex = 0, text;
12624 var lastLineIndex = 0, line = 1;
12724 var arrBlocks = self._arrBlocks;
128
12924 self.fire('start', {
130 pos: 0,
131 line: 1,
132 col: 1
133 });
134
13524 while((match = regTag.exec(html))){
13642 matchIndex = match.index;
13742 if(matchIndex > lastIndex){//保存前面的文本或者CDATA
1387 text = html.substring(lastIndex, matchIndex);
1397 if(tagCDATA){
1402 arrCDATA.push(text);
141 }
142 else{//文本
1435 saveBlock('text', text, lastIndex);
144 }
145 }
14642 lastIndex = regTag.lastIndex;
147
14842 if((tagName = match[1])){
14912 if(tagCDATA && tagName === tagCDATA){//结束标签前输出CDATA
1502 text = arrCDATA.join('');
1512 saveBlock('cdata', text, lastCDATAIndex, {
152 'tagName': tagCDATA
153 });
1542 tagCDATA = null;
1552 arrCDATA = null;
156 }
15712 if(!tagCDATA){
158 //标签结束
15912 saveBlock('tagend', match[0], matchIndex, {
160 'tagName': tagName
161 });
16212 continue;
163 }
164 }
165
16630 if(tagCDATA){
1670 arrCDATA.push(match[0]);
168 }
169 else{
17030 if((tagName = match[4])){//标签开始
17120 arrAttrs = [];
17220 var attrs = match[5], attrMatch;
17320 while((attrMatch = regAttr.exec(attrs))){
17413 var name = attrMatch[1],
175 quote = attrMatch[2] ? attrMatch[2] :
176 attrMatch[4] ? attrMatch[4] : '',
177 value = attrMatch[3] ? attrMatch[3] :
178 attrMatch[5] ? attrMatch[5] :
179 attrMatch[6] ? attrMatch[6] : '';
18013 arrAttrs.push({'name': name, 'value': value, 'quote': quote, 'index': attrMatch.index, 'raw': attrMatch[0]});
181 }
18220 saveBlock('tagstart', match[0], matchIndex, {
183 'tagName': tagName,
184 'attrs': arrAttrs,
185 'close': match[6]
186 });
18720 if(mapCdataTags[tagName]){
1882 tagCDATA = tagName;
1892 arrCDATA = [];
1902 lastCDATAIndex = lastIndex;
191 }
192 }
19310 else if(match[2] || match[3]){//注释标签
19410 saveBlock('comment', match[0], matchIndex, {
195 'content': match[2] || match[3],
196 'long': match[2]?true:false
197 });
198 }
199 }
200 }
201
20224 if(html.length > lastIndex){
203 //结尾文本
2041 text = html.substring(lastIndex, html.length);
2051 saveBlock('text', text, lastIndex);
206 }
207
20824 self.fire('end', {
209 pos: lastIndex,
210 line: line,
211 col: lastIndex - lastLineIndex + 1
212 });
213
214 //存储区块
21524 function saveBlock(type, raw, pos, data){
21650 var col = pos - lastLineIndex + 1;
21750 if(data === undefined){
2186 data = {};
219 }
22050 data.raw = raw;
22150 data.pos = pos;
22250 data.line = line;
22350 data.col = col;
22450 arrBlocks.push(data);
22550 self.fire(type, data);
22650 var lineMatch;
22750 while((lineMatch = regLine.exec(raw))){
2283 line ++;
2293 lastLineIndex = pos + regLine.lastIndex;
230 }
231 }
232
233 },
234
235 addListener: function(types, listener){
23624 var _listeners = this._listeners;
23724 var arrTypes = types.split(/[,\s]/), type;
23824 for(var i=0, l = arrTypes.length;i<l;i++){
23924 type = arrTypes[i];
24024 if (_listeners[type] === undefined){
24124 _listeners[type] = [];
242 }
24324 _listeners[type].push(listener);
244 }
245 },
246
247 fire: function(type, data){
24898 if (data === undefined){
2490 data = {};
250 }
25198 data.type = type;
25298 var self = this,
253 listeners = [],
254 listenersType = self._listeners[type],
255 listenersAll = self._listeners['all'];
25698 if (listenersType !== undefined){
2575 listeners = listeners.concat(listenersType);
258 }
25998 if (listenersAll !== undefined){
26083 listeners = listeners.concat(listenersAll);
261 }
26298 for (var i = 0, l = listeners.length; i < l; i++){
26388 listeners[i].call(self, data);
264 }
265 },
266
267 removeListener: function(type, listener){
2680 var listenersType = this._listeners[type];
2690 if(listenersType !== undefined){
2700 for (var i = 0, l = listenersType.length; i < l; i++){
2710 if (listenersType[i] === listener){
2720 listenersType.splice(i, 1);
2730 break;
274 }
275 }
276 }
277 }
278 };
279
2801 return HTMLParser;
281
282})();
283
2841if (typeof exports === 'object' && exports){
2851 exports.HTMLParser = HTMLParser;
286}
287/**
288 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
289 * MIT Licensed
290 */
2911HTMLHint.addRule({
292 id: 'attr-lowercase',
293 description: 'Attribute name must be lowercase.',
294 init: function(parser, reporter){
2952 var self = this;
2962 parser.addListener('tagstart', function(event){
2972 var attrs = event.attrs,
298 attr,
299 col = event.col + event.tagName.length + 1;
3002 for(var i=0, l=attrs.length;i<l;i++){
3012 attr = attrs[i];
3022 var attrName = attr.name;
3032 if(attrName !== attrName.toLowerCase()){
3041 reporter.error('Attribute name [ '+attrName+' ] must be lower case.', event.line, col + attr.index, self, attr.raw);
305 }
306 }
307 });
308 }
309});
310/**
311 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
312 * MIT Licensed
313 */
3141HTMLHint.addRule({
315 id: 'attr-value-double-quotes',
316 description: 'Attribute value must closed by double quotes.',
317 init: function(parser, reporter){
3183 var self = this;
3193 parser.addListener('tagstart', function(event){
3203 var attrs = event.attrs,
321 attr,
322 col = event.col + event.tagName.length + 1;
3233 for(var i=0, l=attrs.length;i<l;i++){
3243 attr = attrs[i];
3253 if(attr.quote !== '' && attr.quote !== '"'){
3261 reporter.error('The value of attribute [ '+attr.name+' ] must closed by double quotes.', event.line, col + attr.index, self, attr.raw);
327 }
328 }
329 });
330 }
331});
332/**
333 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
334 * MIT Licensed
335 */
3361HTMLHint.addRule({
337 id: 'attr-value-empty',
338 description: 'Attribute must set value.',
339 init: function(parser, reporter){
3400 var self = this;
3410 parser.addListener('tagstart', function(event){
3420 var attrs = event.attrs,
343 attr,
344 col = event.col + event.tagName.length + 1;
3450 for(var i=0, l=attrs.length;i<l;i++){
3460 attr = attrs[i];
3470 if(attr.quote === '' && attr.value === ''){
3480 reporter.error('The attribute [ '+attr.name+' ] must set value.', event.line, col + attr.index, self, attr.raw);
349 }
350 }
351 });
352 }
353});
354/**
355 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
356 * MIT Licensed
357 */
3581HTMLHint.addRule({
359 id: 'doctype-first',
360 description: 'Doctype must be first.',
361 init: function(parser, reporter){
3620 var self = this;
3630 var allEvent = function(event){
3640 if(event.type === 'start' || (event.type === 'text' && /^\s*$/.test(event.raw))){
3650 return;
366 }
3670 if((event.type !== 'comment' && event.long === false) || /^DOCTYPE\s+/i.test(event.content) === false){
3680 reporter.error('Doctype must be first.', event.line, event.col, self, event.raw);
369 }
3700 parser.removeListener('all', allEvent);
371 };
3720 parser.addListener('all', allEvent);
373 }
374});
375/**
376 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
377 * MIT Licensed
378 */
3791HTMLHint.addRule({
380 id: 'doctype-html5',
381 description: 'Doctype must be html5.',
382 init: function(parser, reporter){
3830 var self = this;
3840 function onComment(event){
3850 if(event.long === false && event.content.toLowerCase() !== 'doctype html'){
3860 reporter.error('Doctype must be html5.', event.line, event.col, self, event.raw);
387 }
388 }
3890 function onTagStart(){
3900 parser.removeListener('comment', onComment);
3910 parser.removeListener('tagstart', onTagStart);
392 }
3930 parser.addListener('all', onComment);
3940 parser.addListener('tagstart', onTagStart);
395 }
396});
397/**
398 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
399 * MIT Licensed
400 */
4011HTMLHint.addRule({
402 id: 'head-script-disabled',
403 description: 'The script tag can not be used in head.',
404 init: function(parser, reporter){
4050 var self = this;
4060 function onTagStart(event){
4070 if(event.tagName.toLowerCase() === 'script'){
4080 reporter.error('The script tag can not be used in head.', event.line, event.col, self, event.raw);
409 }
410 }
4110 function onTagEnd(event){
4120 if(event.tagName.toLowerCase() === 'head'){
4130 parser.removeListener('tagstart', onTagStart);
4140 parser.removeListener('tagstart', onTagEnd);
415 }
416 }
4170 parser.addListener('tagstart', onTagStart);
4180 parser.addListener('tagend', onTagEnd);
419 }
420});
421/**
422 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
423 * MIT Licensed
424 */
4251HTMLHint.addRule({
426 id: 'id-class-value',
427 description: 'Id and class value must meet some rules.',
428 init: function(parser, reporter, options){
4290 var self = this;
4300 var arrRules = {
431 'underline': {
432 'regId': /^[a-z\d]+(_[a-z\d]+)*$/,
433 'message': 'Id and class value must lower case and split by underline.'
434 },
435 'dash': {
436 'regId': /^[a-z\d]+(-[a-z\d]+)*$/,
437 'message': 'Id and class value must lower case and split by dash.'
438 },
439 'hump': {
440 'regId': /^[a-z][a-zA-Z\d]*([A-Z][a-zA-Z\d]*)*$/,
441 'message': 'Id and class value must meet hump style.'
442 }
443 }, rule;
4440 if(typeof options === 'string'){
4450 rule = arrRules[options];
446 }
447 else{
4480 rule = options;
449 }
4500 if(rule && rule.regId){
4510 var regId = rule.regId,
452 message = rule.message;
4530 parser.addListener('tagstart', function(event){
4540 var attrs = event.attrs, attr;
4550 for(var i=0, l1=attrs.length;i<l1;i++){
4560 attr = attrs[i];
4570 if(attr.name.toLowerCase() === 'id'){
4580 if(regId.test(attr.value) === false){
4590 reporter.error(message, event.line, event.col, self, attr.raw);
460 }
461 }
4620 if(attr.name.toLowerCase() === 'class'){
4630 var arrClass = attr.value.split(/\s+/g), classValue;
4640 for(var j=0, l2=arrClass.length;j<l2;j++){
4650 classValue = arrClass[j];
4660 if(classValue && regId.test(classValue) === false){
4670 reporter.error(message, event.line, event.col, self, classValue);
468 }
469 }
470 }
471 }
472 });
473 }
474 }
475});
476/**
477 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
478 * MIT Licensed
479 */
4801HTMLHint.addRule({
481 id: 'img-alt-require',
482 description: 'Alt of img tag must be set value.',
483 init: function(parser, reporter){
4840 var self = this;
4850 parser.addListener('tagstart', function(event){
4860 if(event.tagName.toLowerCase() === 'img'){
4870 var attrs = event.attrs;
4880 var haveAlt = false;
4890 for(var i=0, l=attrs.length;i<l;i++){
4900 if(attrs[i].name.toLowerCase() === 'alt'){
4910 haveAlt = true;
4920 break;
493 }
494 }
4950 if(haveAlt === false){
4960 reporter.error('Alt of img tag must be set value.', event.line, event.col, self, event.raw);
497 }
498 }
499 });
500 }
501});
502/**
503 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
504 * MIT Licensed
505 */
5061HTMLHint.addRule({
507 id: 'spec-char-escape',
508 description: 'Special characters must be escaped.',
509 init: function(parser, reporter){
5100 var self = this;
5110 parser.addListener('text', function(event){
5120 var match = event.raw.match(/[<>]/);
5130 if(match !== null){
5140 reporter.error('Special characters must be escaped : [ '+match[0]+' ].', event.line, event.col + match.index, self, event.raw);
515 }
516 });
517 }
518});
519/**
520 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
521 * MIT Licensed
522 */
5231HTMLHint.addRule({
524 id: 'style-disabled',
525 description: 'Style tag can not be use.',
526 init: function(parser, reporter){
5270 var self = this;
5280 parser.addListener('tagstart', function(event){
5290 if(event.tagName.toLowerCase() === 'style'){
5300 reporter.error('Style tag can not be use.', event.line, event.col, self, event.raw);
531 }
532 });
533 }
534});
535/**
536 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
537 * MIT Licensed
538 */
5391HTMLHint.addRule({
540 id: 'tag-pair',
541 description: 'Tag must be paired.',
542 init: function(parser, reporter){
5430 var self = this;
5440 var stack=[],
545 mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01
5460 parser.addListener('tagstart', function(event){
5470 var tagName = event.tagName.toLowerCase();
5480 if (mapEmptyTags[tagName] === undefined && !event.close){
5490 stack.push(tagName);
550 }
551 });
5520 parser.addListener('tagend', function(event){
5530 var tagName = event.tagName.toLowerCase();
554 //向上寻找匹配的开始标签
5550 for(var pos = stack.length-1;pos >= 0; pos--){
5560 if(stack[pos] === tagName){
5570 break;
558 }
559 }
5600 if(pos >= 0){
5610 var arrTags = [];
5620 for(var i=stack.length-1;i>pos;i--){
5630 arrTags.push('</'+stack[i]+'>');
564 }
5650 if(arrTags.length > 0){
5660 reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, event.raw);
567 }
5680 stack.length=pos;
569 }
570 else{
5710 reporter.error('Tag must be paired, No start tag.', event.line, event.col, self, event.raw);
572 }
573 });
5740 parser.addListener('end', function(event){
5750 var arrTags = [];
5760 for(var i=stack.length-1;i>=0;i--){
5770 arrTags.push('</'+stack[i]+'>');
578 }
5790 if(arrTags.length > 0){
5800 reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, event.raw);
581 }
582 });
583 }
584});
585/**
586 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
587 * MIT Licensed
588 */
5891HTMLHint.addRule({
590 id: 'tag-self-close',
591 description: 'The empty tag must closed by self.',
592 init: function(parser, reporter){
5930 var self = this;
5940 var mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01
5950 parser.addListener('tagstart', function(event){
5960 var tagName = event.tagName.toLowerCase();
5970 if(mapEmptyTags[tagName] !== undefined){
5980 if(!event.close){
5990 reporter.error('The empty tag must closed by self.', event.line, event.col, self, event.raw);
600 }
601 }
602 });
603 }
604});
605/**
606 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
607 * MIT Licensed
608 */
6091HTMLHint.addRule({
610 id: 'tagname-lowercase',
611 description: 'Tagname must be lowercase.',
612 init: function(parser, reporter){
6130 var self = this;
6140 parser.addListener('tagstart,tagend', function(event){
6150 var tagName = event.tagName;
6160 if(tagName !== tagName.toLowerCase()){
6170 reporter.error('Tagname [ '+tagName+' ] must be lower case.', event.line, event.col, self, event.raw);
618 }
619 });
620 }
621});
\ No newline at end of file +

Coverage

84%
267
225
42

htmlhint.js

84%
267
225
42
LineHitsSource
1/**
2 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
3 * MIT Licensed
4 */
51var HTMLHint = (function (undefined) {
6
71 var rules = [];
8
91 var HTMLHint = {};
10
111 HTMLHint.version = '@VERSION';
12
131 HTMLHint.addRule = function(rule){
1413 rules[rule.id] = rule;
15 };
16
171 HTMLHint.verify = function(html, ruleset){
1828 var parser = new HTMLParser(),
19 reporter = new HTMLHint.Reporter(html.split(/\r?\n/), ruleset);
20
2128 var rule;
2228 for (var id in ruleset){
2328 rule = rules[id];
2428 if (rule !== undefined){
2528 rule.init(parser, reporter, ruleset[id]);
26 }
27 }
28
2928 parser.parse(html);
30
3128 return reporter.messages;
32 };
33
341 return HTMLHint;
35
36})();
37
381if (typeof exports === 'object' && exports){
391 exports.HTMLHint = HTMLHint;
40}
41/**
42 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
43 * MIT Licensed
44 */
451(function(HTMLHint, undefined){
46
471 var Reporter = function(){
4828 var self = this;
4928 self._init.apply(self,arguments);
50 };
51
521 Reporter.prototype = {
53 _init: function(lines, ruleset){
5428 var self = this;
5528 self.lines = lines;
5628 self.ruleset = ruleset;
5728 self.messages = [];
58 },
59 //错误
60 error: function(message, line, col, rule, raw){
6117 this.report('error', message, line, col, rule, raw);
62 },
63 //警告
64 warn: function(message, line, col, rule, raw){
650 this.report('warning', message, line, col, rule, raw);
66 },
67 //信息
68 info: function(message, line, col, rule, raw){
690 this.report('info', message, line, col, rule, raw);
70 },
71 //报告
72 report: function(type, message, line, col, rule, raw){
7317 var self = this;
7417 self.messages.push({
75 type: type,
76 message: message,
77 raw: raw,
78 evidence: self.lines[line-1],
79 line: line,
80 col: col,
81 rule: rule
82 });
83 }
84 };
85
861 HTMLHint.Reporter = Reporter;
87
88})(HTMLHint);
89/**
90 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
91 * MIT Licensed
92 */
931var HTMLParser = (function(undefined){
94
951 var HTMLParser = function(){
9647 var self = this;
9747 self._init.apply(self,arguments);
98 };
99
1001 HTMLParser.prototype = {
101 _init: function(){
10247 var self = this;
10347 self._listeners = {};
10447 self._mapCdataTags = self.makeMap("script,style");
10547 self._arrBlocks = [];
106 },
107
108 makeMap: function(str){
10947 var obj = {}, items = str.split(",");
11047 for ( var i = 0; i < items.length; i++ ){
11194 obj[ items[i] ] = true;
112 }
11347 return obj;
114 },
115
116 parse: function(html){
117
11847 var self = this,
119 mapCdataTags = self._mapCdataTags;
120
12147 var regTag=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:"[^"]*"|'[^']*'|[^"'<>])*?)\s*(\/?))>/g,
122 regAttr = /\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s]+)))?/g,
123 regLine = /\r?\n/g;
124
12547 var match, matchIndex, lastIndex = 0, tagName, arrAttrs, tagCDATA, arrCDATA, lastCDATAIndex = 0, text;
12647 var lastLineIndex = 0, line = 1;
12747 var arrBlocks = self._arrBlocks;
128
12947 self.fire('start', {
130 pos: 0,
131 line: 1,
132 col: 1
133 });
134
13547 while((match = regTag.exec(html))){
13688 matchIndex = match.index;
13788 if(matchIndex > lastIndex){//保存前面的文本或者CDATA
13811 text = html.substring(lastIndex, matchIndex);
13911 if(tagCDATA){
1404 arrCDATA.push(text);
141 }
142 else{//文本
1437 saveBlock('text', text, lastIndex);
144 }
145 }
14688 lastIndex = regTag.lastIndex;
147
14888 if((tagName = match[1])){
14926 if(tagCDATA && tagName === tagCDATA){//结束标签前输出CDATA
1506 text = arrCDATA.join('');
1516 saveBlock('cdata', text, lastCDATAIndex, {
152 'tagName': tagCDATA
153 });
1546 tagCDATA = null;
1556 arrCDATA = null;
156 }
15726 if(!tagCDATA){
158 //标签结束
15926 saveBlock('tagend', match[0], matchIndex, {
160 'tagName': tagName
161 });
16226 continue;
163 }
164 }
165
16662 if(tagCDATA){
1670 arrCDATA.push(match[0]);
168 }
169 else{
17062 if((tagName = match[4])){//标签开始
17149 arrAttrs = [];
17249 var attrs = match[5], attrMatch;
17349 while((attrMatch = regAttr.exec(attrs))){
17440 var name = attrMatch[1],
175 quote = attrMatch[2] ? attrMatch[2] :
176 attrMatch[4] ? attrMatch[4] : '',
177 value = attrMatch[3] ? attrMatch[3] :
178 attrMatch[5] ? attrMatch[5] :
179 attrMatch[6] ? attrMatch[6] : '';
18040 arrAttrs.push({'name': name, 'value': value, 'quote': quote, 'index': attrMatch.index, 'raw': attrMatch[0]});
181 }
18249 saveBlock('tagstart', match[0], matchIndex, {
183 'tagName': tagName,
184 'attrs': arrAttrs,
185 'close': match[6]
186 });
18749 if(mapCdataTags[tagName]){
1886 tagCDATA = tagName;
1896 arrCDATA = [];
1906 lastCDATAIndex = lastIndex;
191 }
192 }
19313 else if(match[2] || match[3]){//注释标签
19413 saveBlock('comment', match[0], matchIndex, {
195 'content': match[2] || match[3],
196 'long': match[2]?true:false
197 });
198 }
199 }
200 }
201
20247 if(html.length > lastIndex){
203 //结尾文本
2041 text = html.substring(lastIndex, html.length);
2051 saveBlock('text', text, lastIndex);
206 }
207
20847 self.fire('end', {
209 pos: lastIndex,
210 line: line,
211 col: lastIndex - lastLineIndex + 1
212 });
213
214 //存储区块
21547 function saveBlock(type, raw, pos, data){
216102 var col = pos - lastLineIndex + 1;
217102 if(data === undefined){
2188 data = {};
219 }
220102 data.raw = raw;
221102 data.pos = pos;
222102 data.line = line;
223102 data.col = col;
224102 arrBlocks.push(data);
225102 self.fire(type, data);
226102 var lineMatch;
227102 while((lineMatch = regLine.exec(raw))){
2283 line ++;
2293 lastLineIndex = pos + regLine.lastIndex;
230 }
231 }
232
233 },
234
235 addListener: function(types, listener){
23652 var _listeners = this._listeners;
23752 var arrTypes = types.split(/[,\s]/), type;
23852 for(var i=0, l = arrTypes.length;i<l;i++){
23952 type = arrTypes[i];
24052 if (_listeners[type] === undefined){
24152 _listeners[type] = [];
242 }
24352 _listeners[type].push(listener);
244 }
245 },
246
247 fire: function(type, data){
248196 if (data === undefined){
2490 data = {};
250 }
251196 data.type = type;
252196 var self = this,
253 listeners = [],
254 listenersType = self._listeners[type],
255 listenersAll = self._listeners['all'];
256196 if (listenersType !== undefined){
25739 listeners = listeners.concat(listenersType);
258 }
259196 if (listenersAll !== undefined){
260100 listeners = listeners.concat(listenersAll);
261 }
262196 for (var i = 0, l = listeners.length; i < l; i++){
263133 listeners[i].call(self, data);
264 }
265 },
266
267 removeListener: function(type, listener){
26812 var listenersType = this._listeners[type];
26912 if(listenersType !== undefined){
27010 for (var i = 0, l = listenersType.length; i < l; i++){
2717 if (listenersType[i] === listener){
2727 listenersType.splice(i, 1);
2737 break;
274 }
275 }
276 }
277 }
278 };
279
2801 return HTMLParser;
281
282})();
283
2841if (typeof exports === 'object' && exports){
2851 exports.HTMLParser = HTMLParser;
286}
287/**
288 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
289 * MIT Licensed
290 */
2911HTMLHint.addRule({
292 id: 'attr-lowercase',
293 description: 'Attribute name must be lowercase.',
294 init: function(parser, reporter){
2952 var self = this;
2962 parser.addListener('tagstart', function(event){
2972 var attrs = event.attrs,
298 attr,
299 col = event.col + event.tagName.length + 1;
3002 for(var i=0, l=attrs.length;i<l;i++){
3012 attr = attrs[i];
3022 var attrName = attr.name;
3032 if(attrName !== attrName.toLowerCase()){
3041 reporter.error('Attribute name [ '+attrName+' ] must be lower case.', event.line, col + attr.index, self, attr.raw);
305 }
306 }
307 });
308 }
309});
310/**
311 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
312 * MIT Licensed
313 */
3141HTMLHint.addRule({
315 id: 'attr-value-double-quotes',
316 description: 'Attribute value must closed by double quotes.',
317 init: function(parser, reporter){
3183 var self = this;
3193 parser.addListener('tagstart', function(event){
3203 var attrs = event.attrs,
321 attr,
322 col = event.col + event.tagName.length + 1;
3233 for(var i=0, l=attrs.length;i<l;i++){
3243 attr = attrs[i];
3253 if(attr.quote !== '' && attr.quote !== '"'){
3261 reporter.error('The value of attribute [ '+attr.name+' ] must closed by double quotes.', event.line, col + attr.index, self, attr.raw);
327 }
328 }
329 });
330 }
331});
332/**
333 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
334 * MIT Licensed
335 */
3361HTMLHint.addRule({
337 id: 'attr-value-empty',
338 description: 'Attribute must set value.',
339 init: function(parser, reporter){
3403 var self = this;
3413 parser.addListener('tagstart', function(event){
3423 var attrs = event.attrs,
343 attr,
344 col = event.col + event.tagName.length + 1;
3453 for(var i=0, l=attrs.length;i<l;i++){
3463 attr = attrs[i];
3473 if(attr.quote === '' && attr.value === ''){
3481 reporter.error('The attribute [ '+attr.name+' ] must set value.', event.line, col + attr.index, self, attr.raw);
349 }
350 }
351 });
352 }
353});
354/**
355 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
356 * MIT Licensed
357 */
3581HTMLHint.addRule({
359 id: 'doctype-first',
360 description: 'Doctype must be first.',
361 init: function(parser, reporter){
3622 var self = this;
3632 var allEvent = function(event){
3644 if(event.type === 'start' || (event.type === 'text' && /^\s*$/.test(event.raw))){
3652 return;
366 }
3672 if((event.type !== 'comment' && event.long === false) || /^DOCTYPE\s+/i.test(event.content) === false){
3681 reporter.error('Doctype must be first.', event.line, event.col, self, event.raw);
369 }
3702 parser.removeListener('all', allEvent);
371 };
3722 parser.addListener('all', allEvent);
373 }
374});
375/**
376 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
377 * MIT Licensed
378 */
3791HTMLHint.addRule({
380 id: 'doctype-html5',
381 description: 'Doctype must be html5.',
382 init: function(parser, reporter){
3832 var self = this;
3842 function onComment(event){
3859 if(event.long === false && event.content.toLowerCase() !== 'doctype html'){
3861 reporter.error('Doctype must be html5.', event.line, event.col, self, event.raw);
387 }
388 }
3892 function onTagStart(){
3902 parser.removeListener('comment', onComment);
3912 parser.removeListener('tagstart', onTagStart);
392 }
3932 parser.addListener('all', onComment);
3942 parser.addListener('tagstart', onTagStart);
395 }
396});
397/**
398 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
399 * MIT Licensed
400 */
4011HTMLHint.addRule({
402 id: 'head-script-disabled',
403 description: 'The script tag can not be used in head.',
404 init: function(parser, reporter){
4053 var self = this;
4063 function onTagStart(event){
4075 if(event.tagName.toLowerCase() === 'script'){
4082 reporter.error('The script tag can not be used in head.', event.line, event.col, self, event.raw);
409 }
410 }
4113 function onTagEnd(event){
4127 if(event.tagName.toLowerCase() === 'head'){
4133 parser.removeListener('tagstart', onTagStart);
4143 parser.removeListener('tagstart', onTagEnd);
415 }
416 }
4173 parser.addListener('tagstart', onTagStart);
4183 parser.addListener('tagend', onTagEnd);
419 }
420});
421/**
422 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
423 * MIT Licensed
424 */
4251HTMLHint.addRule({
426 id: 'id-class-value',
427 description: 'Id and class value must meet some rules.',
428 init: function(parser, reporter, options){
4296 var self = this;
4306 var arrRules = {
431 'underline': {
432 'regId': /^[a-z\d]+(_[a-z\d]+)*$/,
433 'message': 'Id and class value must lower case and split by underline.'
434 },
435 'dash': {
436 'regId': /^[a-z\d]+(-[a-z\d]+)*$/,
437 'message': 'Id and class value must lower case and split by dash.'
438 },
439 'hump': {
440 'regId': /^[a-z][a-zA-Z\d]*([A-Z][a-zA-Z\d]*)*$/,
441 'message': 'Id and class value must meet hump style.'
442 }
443 }, rule;
4446 if(typeof options === 'string'){
4456 rule = arrRules[options];
446 }
447 else{
4480 rule = options;
449 }
4506 if(rule && rule.regId){
4516 var regId = rule.regId,
452 message = rule.message;
4536 parser.addListener('tagstart', function(event){
4546 var attrs = event.attrs,
455 attr,
456 col = event.col + event.tagName.length + 1;
4576 for(var i=0, l1=attrs.length;i<l1;i++){
45812 attr = attrs[i];
45912 if(attr.name.toLowerCase() === 'id'){
4606 if(regId.test(attr.value) === false){
4613 reporter.error(message, event.line, col + attr.index, self, attr.raw);
462 }
463 }
46412 if(attr.name.toLowerCase() === 'class'){
4656 var arrClass = attr.value.split(/\s+/g), classValue;
4666 for(var j=0, l2=arrClass.length;j<l2;j++){
4676 classValue = arrClass[j];
4686 if(classValue && regId.test(classValue) === false){
4693 reporter.error(message, event.line, col + attr.index, self, classValue);
470 }
471 }
472 }
473 }
474 });
475 }
476 }
477});
478/**
479 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
480 * MIT Licensed
481 */
4821HTMLHint.addRule({
483 id: 'img-alt-require',
484 description: 'Alt of img tag must be set value.',
485 init: function(parser, reporter){
4863 var self = this;
4873 parser.addListener('tagstart', function(event){
4883 if(event.tagName.toLowerCase() === 'img'){
4893 var attrs = event.attrs;
4903 var haveAlt = false;
4913 for(var i=0, l=attrs.length;i<l;i++){
4928 if(attrs[i].name.toLowerCase() === 'alt'){
4932 haveAlt = true;
4942 break;
495 }
496 }
4973 if(haveAlt === false){
4981 reporter.error('Alt of img tag must be set value.', event.line, event.col, self, event.raw);
499 }
500 }
501 });
502 }
503});
504/**
505 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
506 * MIT Licensed
507 */
5081HTMLHint.addRule({
509 id: 'spec-char-escape',
510 description: 'Special characters must be escaped.',
511 init: function(parser, reporter){
5122 var self = this;
5132 parser.addListener('text', function(event){
5142 var raw = event.raw,
515 reSpecChar = /[<>]/g,
516 match;
5172 while((match = reSpecChar.exec(raw))){
5182 reporter.error('Special characters must be escaped : [ '+match[0]+' ].', event.line, event.col + match.index, self, event.raw);
519 }
520 });
521 }
522});
523/**
524 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
525 * MIT Licensed
526 */
5271HTMLHint.addRule({
528 id: 'style-disabled',
529 description: 'Style tag can not be use.',
530 init: function(parser, reporter){
5312 var self = this;
5322 parser.addListener('tagstart', function(event){
5334 if(event.tagName.toLowerCase() === 'style'){
5341 reporter.error('Style tag can not be use.', event.line, event.col, self, event.raw);
535 }
536 });
537 }
538});
539/**
540 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
541 * MIT Licensed
542 */
5431HTMLHint.addRule({
544 id: 'tag-pair',
545 description: 'Tag must be paired.',
546 init: function(parser, reporter){
5470 var self = this;
5480 var stack=[],
549 mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01
5500 parser.addListener('tagstart', function(event){
5510 var tagName = event.tagName.toLowerCase();
5520 if (mapEmptyTags[tagName] === undefined && !event.close){
5530 stack.push(tagName);
554 }
555 });
5560 parser.addListener('tagend', function(event){
5570 var tagName = event.tagName.toLowerCase();
558 //向上寻找匹配的开始标签
5590 for(var pos = stack.length-1;pos >= 0; pos--){
5600 if(stack[pos] === tagName){
5610 break;
562 }
563 }
5640 if(pos >= 0){
5650 var arrTags = [];
5660 for(var i=stack.length-1;i>pos;i--){
5670 arrTags.push('</'+stack[i]+'>');
568 }
5690 if(arrTags.length > 0){
5700 reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, event.raw);
571 }
5720 stack.length=pos;
573 }
574 else{
5750 reporter.error('Tag must be paired, No start tag.', event.line, event.col, self, event.raw);
576 }
577 });
5780 parser.addListener('end', function(event){
5790 var arrTags = [];
5800 for(var i=stack.length-1;i>=0;i--){
5810 arrTags.push('</'+stack[i]+'>');
582 }
5830 if(arrTags.length > 0){
5840 reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, event.raw);
585 }
586 });
587 }
588});
589/**
590 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
591 * MIT Licensed
592 */
5931HTMLHint.addRule({
594 id: 'tag-self-close',
595 description: 'The empty tag must closed by self.',
596 init: function(parser, reporter){
5970 var self = this;
5980 var mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01
5990 parser.addListener('tagstart', function(event){
6000 var tagName = event.tagName.toLowerCase();
6010 if(mapEmptyTags[tagName] !== undefined){
6020 if(!event.close){
6030 reporter.error('The empty tag must closed by self.', event.line, event.col, self, event.raw);
604 }
605 }
606 });
607 }
608});
609/**
610 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
611 * MIT Licensed
612 */
6131HTMLHint.addRule({
614 id: 'tagname-lowercase',
615 description: 'Tagname must be lowercase.',
616 init: function(parser, reporter){
6170 var self = this;
6180 parser.addListener('tagstart,tagend', function(event){
6190 var tagName = event.tagName;
6200 if(tagName !== tagName.toLowerCase()){
6210 reporter.error('Tagname [ '+tagName+' ] must be lower case.', event.line, event.col, self, event.raw);
622 }
623 });
624 }
625});
\ No newline at end of file diff --git a/lib/htmlhint.js b/lib/htmlhint.js index 2ceef90ba..f98b9dba6 100644 --- a/lib/htmlhint.js +++ b/lib/htmlhint.js @@ -1,3 +1,3 @@ /*! HTMLHint v0.9.1 | (c) 2013 Yanis Wang . MIT Licensed */ -var HTMLHint=function(e){var t=[],a={};return a.version="0.9.1",a.addRule=function(e){t[e.id]=e},a.verify=function(r,n){var i,s=new HTMLParser,o=new a.Reporter(r.split(/\r?\n/),n);for(var l in n)i=t[l],i!==e&&i.init(s,o,n[l]);return s.parse(r),o.messages},a}();"object"==typeof exports&&exports&&(exports.HTMLHint=HTMLHint),function(e){var t=function(){var e=this;e._init.apply(e,arguments)};t.prototype={_init:function(e,t){var a=this;a.lines=e,a.ruleset=t,a.messages=[]},error:function(e,t,a,r,n){this.report("error",e,t,a,r,n)},warn:function(e,t,a,r,n){this.report("warning",e,t,a,r,n)},info:function(e,t,a,r,n){this.report("info",e,t,a,r,n)},report:function(e,t,a,r,n,i){var s=this;s.messages.push({type:e,message:t,raw:i,evidence:s.lines[a-1],line:a,col:r,rule:n})}},e.Reporter=t}(HTMLHint);var HTMLParser=function(e){var t=function(){var e=this;e._init.apply(e,arguments)};return t.prototype={_init:function(){var e=this;e._listeners={},e._mapCdataTags=e.makeMap("script,style"),e._arrBlocks=[]},makeMap:function(e){for(var t={},a=e.split(","),r=0;a.length>r;r++)t[a[r]]=!0;return t},parse:function(t){function a(t,a,r,n){var i=r-v+1;n===e&&(n={}),n.raw=a,n.pos=r,n.line=L,n.col=i,b.push(n),u.fire(t,n);for(var s;s=p.exec(a);)L++,v=r+p.lastIndex}var r,n,i,s,o,l,d,u=this,c=u._mapCdataTags,g=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:"[^"]*"|'[^']*'|[^"'<>])*?)\s*(\/?))>/g,m=/\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s]+)))?/g,p=/\r?\n/g,f=0,h=0,v=0,L=1,b=u._arrBlocks;for(u.fire("start",{pos:0,line:1,col:1});r=g.exec(t);)if(n=r.index,n>f&&(d=t.substring(f,n),o?l.push(d):a("text",d,f)),f=g.lastIndex,!(i=r[1])||(o&&i===o&&(d=l.join(""),a("cdata",d,h,{tagName:o}),o=null,l=null),o))if(o)l.push(r[0]);else if(i=r[4]){s=[];for(var w,H=r[5];w=m.exec(H);){var T=w[1],y=w[2]?w[2]:w[4]?w[4]:"",M=w[3]?w[3]:w[5]?w[5]:w[6]?w[6]:"";s.push({name:T,value:M,quote:y,index:w.index,raw:w[0]})}a("tagstart",r[0],n,{tagName:i,attrs:s,close:r[6]}),c[i]&&(o=i,l=[],h=f)}else(r[2]||r[3])&&a("comment",r[0],n,{content:r[2]||r[3],"long":r[2]?!0:!1});else a("tagend",r[0],n,{tagName:i});t.length>f&&(d=t.substring(f,t.length),a("text",d,f)),u.fire("end",{pos:f,line:L,col:f-v+1})},addListener:function(t,a){for(var r,n=this._listeners,i=t.split(/[,\s]/),s=0,o=i.length;o>s;s++)r=i[s],n[r]===e&&(n[r]=[]),n[r].push(a)},fire:function(t,a){a===e&&(a={}),a.type=t;var r=this,n=[],i=r._listeners[t],s=r._listeners.all;i!==e&&(n=n.concat(i)),s!==e&&(n=n.concat(s));for(var o=0,l=n.length;l>o;o++)n[o].call(r,a)},removeListener:function(t,a){var r=this._listeners[t];if(r!==e)for(var n=0,i=r.length;i>n;n++)if(r[n]===a){r.splice(n,1);break}}},t}();"object"==typeof exports&&exports&&(exports.HTMLParser=HTMLParser),HTMLHint.addRule({id:"attr-lowercase",description:"Attribute name must be lowercase.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var r,n=e.attrs,i=e.col+e.tagName.length+1,s=0,o=n.length;o>s;s++){r=n[s];var l=r.name;l!==l.toLowerCase()&&t.error("Attribute name [ "+l+" ] must be lower case.",e.line,i+r.index,a,r.raw)}})}}),HTMLHint.addRule({id:"attr-value-double-quotes",description:"Attribute value must closed by double quotes.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var r,n=e.attrs,i=e.col+e.tagName.length+1,s=0,o=n.length;o>s;s++)r=n[s],""!==r.quote&&'"'!==r.quote&&t.error("The value of attribute [ "+r.name+" ] must closed by double quotes.",e.line,i+r.index,a,r.raw)})}}),HTMLHint.addRule({id:"attr-value-empty",description:"Attribute must set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var r,n=e.attrs,i=e.col+e.tagName.length+1,s=0,o=n.length;o>s;s++)r=n[s],""===r.quote&&""===r.value&&t.error("The attribute [ "+r.name+" ] must set value.",e.line,i+r.index,a,r.raw)})}}),HTMLHint.addRule({id:"doctype-first",description:"Doctype must be first.",init:function(e,t){var a=this,r=function(n){"start"===n.type||"text"===n.type&&/^\s*$/.test(n.raw)||(("comment"!==n.type&&n.long===!1||/^DOCTYPE\s+/i.test(n.content)===!1)&&t.error("Doctype must be first.",n.line,n.col,a,n.raw),e.removeListener("all",r))};e.addListener("all",r)}}),HTMLHint.addRule({id:"doctype-html5",description:"Doctype must be html5.",init:function(e,t){function a(e){e.long===!1&&"doctype html"!==e.content.toLowerCase()&&t.error("Doctype must be html5.",e.line,e.col,n,e.raw)}function r(){e.removeListener("comment",a),e.removeListener("tagstart",r)}var n=this;e.addListener("all",a),e.addListener("tagstart",r)}}),HTMLHint.addRule({id:"head-script-disabled",description:"The script tag can not be used in head.",init:function(e,t){function a(e){"script"===e.tagName.toLowerCase()&&t.error("The script tag can not be used in head.",e.line,e.col,n,e.raw)}function r(t){"head"===t.tagName.toLowerCase()&&(e.removeListener("tagstart",a),e.removeListener("tagstart",r))}var n=this;e.addListener("tagstart",a),e.addListener("tagend",r)}}),HTMLHint.addRule({id:"id-class-value",description:"Id and class value must meet some rules.",init:function(e,t,a){var r,n=this,i={underline:{regId:/^[a-z\d]+(_[a-z\d]+)*$/,message:"Id and class value must lower case and split by underline."},dash:{regId:/^[a-z\d]+(-[a-z\d]+)*$/,message:"Id and class value must lower case and split by dash."},hump:{regId:/^[a-z][a-zA-Z\d]*([A-Z][a-zA-Z\d]*)*$/,message:"Id and class value must meet hump style."}};if(r="string"==typeof a?i[a]:a,r&&r.regId){var s=r.regId,o=r.message;e.addListener("tagstart",function(e){for(var a,r=e.attrs,i=0,l=r.length;l>i;i++)if(a=r[i],"id"===a.name.toLowerCase()&&s.test(a.value)===!1&&t.error(o,e.line,e.col,n,a.raw),"class"===a.name.toLowerCase())for(var d,u=a.value.split(/\s+/g),c=0,g=u.length;g>c;c++)d=u[c],d&&s.test(d)===!1&&t.error(o,e.line,e.col,n,d)})}}}),HTMLHint.addRule({id:"img-alt-require",description:"Alt of img tag must be set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){if("img"===e.tagName.toLowerCase()){for(var r=e.attrs,n=!1,i=0,s=r.length;s>i;i++)if("alt"===r[i].name.toLowerCase()){n=!0;break}n===!1&&t.error("Alt of img tag must be set value.",e.line,e.col,a,e.raw)}})}}),HTMLHint.addRule({id:"spec-char-escape",description:"Special characters must be escaped.",init:function(e,t){var a=this;e.addListener("text",function(e){var r=e.raw.match(/[<>]/);null!==r&&t.error("Special characters must be escaped : [ "+r[0]+" ].",e.line,e.col+r.index,a,e.raw)})}}),HTMLHint.addRule({id:"style-disabled",description:"Style tag can not be use.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){"style"===e.tagName.toLowerCase()&&t.error("Style tag can not be use.",e.line,e.col,a,e.raw)})}}),HTMLHint.addRule({id:"tag-pair",description:"Tag must be paired.",init:function(e,t){var a=this,r=[],n=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");e.addListener("tagstart",function(e){var t=e.tagName.toLowerCase();void 0!==n[t]||e.close||r.push(t)}),e.addListener("tagend",function(e){for(var n=e.tagName.toLowerCase(),i=r.length-1;i>=0&&r[i]!==n;i--);if(i>=0){for(var s=[],o=r.length-1;o>i;o--)s.push("");s.length>0&&t.error("Tag must be paired, Missing: [ "+s.join("")+" ]",e.line,e.col,a,e.raw),r.length=i}else t.error("Tag must be paired, No start tag.",e.line,e.col,a,e.raw)}),e.addListener("end",function(e){for(var n=[],i=r.length-1;i>=0;i--)n.push("");n.length>0&&t.error("Tag must be paired, Missing: [ "+n.join("")+" ]",e.line,e.col,a,e.raw)})}}),HTMLHint.addRule({id:"tag-self-close",description:"The empty tag must closed by self.",init:function(e,t){var a=this,r=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");e.addListener("tagstart",function(e){var n=e.tagName.toLowerCase();void 0!==r[n]&&(e.close||t.error("The empty tag must closed by self.",e.line,e.col,a,e.raw))})}}),HTMLHint.addRule({id:"tagname-lowercase",description:"Tagname must be lowercase.",init:function(e,t){var a=this;e.addListener("tagstart,tagend",function(e){var r=e.tagName;r!==r.toLowerCase()&&t.error("Tagname [ "+r+" ] must be lower case.",e.line,e.col,a,e.raw)})}}); \ No newline at end of file +var HTMLHint=function(e){var t=[],a={};return a.version="0.9.1",a.addRule=function(e){t[e.id]=e},a.verify=function(r,n){var i,s=new HTMLParser,o=new a.Reporter(r.split(/\r?\n/),n);for(var l in n)i=t[l],i!==e&&i.init(s,o,n[l]);return s.parse(r),o.messages},a}();"object"==typeof exports&&exports&&(exports.HTMLHint=HTMLHint),function(e){var t=function(){var e=this;e._init.apply(e,arguments)};t.prototype={_init:function(e,t){var a=this;a.lines=e,a.ruleset=t,a.messages=[]},error:function(e,t,a,r,n){this.report("error",e,t,a,r,n)},warn:function(e,t,a,r,n){this.report("warning",e,t,a,r,n)},info:function(e,t,a,r,n){this.report("info",e,t,a,r,n)},report:function(e,t,a,r,n,i){var s=this;s.messages.push({type:e,message:t,raw:i,evidence:s.lines[a-1],line:a,col:r,rule:n})}},e.Reporter=t}(HTMLHint);var HTMLParser=function(e){var t=function(){var e=this;e._init.apply(e,arguments)};return t.prototype={_init:function(){var e=this;e._listeners={},e._mapCdataTags=e.makeMap("script,style"),e._arrBlocks=[]},makeMap:function(e){for(var t={},a=e.split(","),r=0;a.length>r;r++)t[a[r]]=!0;return t},parse:function(t){function a(t,a,r,n){var i=r-v+1;n===e&&(n={}),n.raw=a,n.pos=r,n.line=L,n.col=i,b.push(n),u.fire(t,n);for(var s;s=f.exec(a);)L++,v=r+f.lastIndex}var r,n,i,s,o,l,d,u=this,c=u._mapCdataTags,g=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:"[^"]*"|'[^']*'|[^"'<>])*?)\s*(\/?))>/g,m=/\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s]+)))?/g,f=/\r?\n/g,p=0,h=0,v=0,L=1,b=u._arrBlocks;for(u.fire("start",{pos:0,line:1,col:1});r=g.exec(t);)if(n=r.index,n>p&&(d=t.substring(p,n),o?l.push(d):a("text",d,p)),p=g.lastIndex,!(i=r[1])||(o&&i===o&&(d=l.join(""),a("cdata",d,h,{tagName:o}),o=null,l=null),o))if(o)l.push(r[0]);else if(i=r[4]){s=[];for(var w,H=r[5];w=m.exec(H);){var T=w[1],y=w[2]?w[2]:w[4]?w[4]:"",x=w[3]?w[3]:w[5]?w[5]:w[6]?w[6]:"";s.push({name:T,value:x,quote:y,index:w.index,raw:w[0]})}a("tagstart",r[0],n,{tagName:i,attrs:s,close:r[6]}),c[i]&&(o=i,l=[],h=p)}else(r[2]||r[3])&&a("comment",r[0],n,{content:r[2]||r[3],"long":r[2]?!0:!1});else a("tagend",r[0],n,{tagName:i});t.length>p&&(d=t.substring(p,t.length),a("text",d,p)),u.fire("end",{pos:p,line:L,col:p-v+1})},addListener:function(t,a){for(var r,n=this._listeners,i=t.split(/[,\s]/),s=0,o=i.length;o>s;s++)r=i[s],n[r]===e&&(n[r]=[]),n[r].push(a)},fire:function(t,a){a===e&&(a={}),a.type=t;var r=this,n=[],i=r._listeners[t],s=r._listeners.all;i!==e&&(n=n.concat(i)),s!==e&&(n=n.concat(s));for(var o=0,l=n.length;l>o;o++)n[o].call(r,a)},removeListener:function(t,a){var r=this._listeners[t];if(r!==e)for(var n=0,i=r.length;i>n;n++)if(r[n]===a){r.splice(n,1);break}}},t}();"object"==typeof exports&&exports&&(exports.HTMLParser=HTMLParser),HTMLHint.addRule({id:"attr-lowercase",description:"Attribute name must be lowercase.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var r,n=e.attrs,i=e.col+e.tagName.length+1,s=0,o=n.length;o>s;s++){r=n[s];var l=r.name;l!==l.toLowerCase()&&t.error("Attribute name [ "+l+" ] must be lower case.",e.line,i+r.index,a,r.raw)}})}}),HTMLHint.addRule({id:"attr-value-double-quotes",description:"Attribute value must closed by double quotes.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var r,n=e.attrs,i=e.col+e.tagName.length+1,s=0,o=n.length;o>s;s++)r=n[s],""!==r.quote&&'"'!==r.quote&&t.error("The value of attribute [ "+r.name+" ] must closed by double quotes.",e.line,i+r.index,a,r.raw)})}}),HTMLHint.addRule({id:"attr-value-empty",description:"Attribute must set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var r,n=e.attrs,i=e.col+e.tagName.length+1,s=0,o=n.length;o>s;s++)r=n[s],""===r.quote&&""===r.value&&t.error("The attribute [ "+r.name+" ] must set value.",e.line,i+r.index,a,r.raw)})}}),HTMLHint.addRule({id:"doctype-first",description:"Doctype must be first.",init:function(e,t){var a=this,r=function(n){"start"===n.type||"text"===n.type&&/^\s*$/.test(n.raw)||(("comment"!==n.type&&n.long===!1||/^DOCTYPE\s+/i.test(n.content)===!1)&&t.error("Doctype must be first.",n.line,n.col,a,n.raw),e.removeListener("all",r))};e.addListener("all",r)}}),HTMLHint.addRule({id:"doctype-html5",description:"Doctype must be html5.",init:function(e,t){function a(e){e.long===!1&&"doctype html"!==e.content.toLowerCase()&&t.error("Doctype must be html5.",e.line,e.col,n,e.raw)}function r(){e.removeListener("comment",a),e.removeListener("tagstart",r)}var n=this;e.addListener("all",a),e.addListener("tagstart",r)}}),HTMLHint.addRule({id:"head-script-disabled",description:"The script tag can not be used in head.",init:function(e,t){function a(e){"script"===e.tagName.toLowerCase()&&t.error("The script tag can not be used in head.",e.line,e.col,n,e.raw)}function r(t){"head"===t.tagName.toLowerCase()&&(e.removeListener("tagstart",a),e.removeListener("tagstart",r))}var n=this;e.addListener("tagstart",a),e.addListener("tagend",r)}}),HTMLHint.addRule({id:"id-class-value",description:"Id and class value must meet some rules.",init:function(e,t,a){var r,n=this,i={underline:{regId:/^[a-z\d]+(_[a-z\d]+)*$/,message:"Id and class value must lower case and split by underline."},dash:{regId:/^[a-z\d]+(-[a-z\d]+)*$/,message:"Id and class value must lower case and split by dash."},hump:{regId:/^[a-z][a-zA-Z\d]*([A-Z][a-zA-Z\d]*)*$/,message:"Id and class value must meet hump style."}};if(r="string"==typeof a?i[a]:a,r&&r.regId){var s=r.regId,o=r.message;e.addListener("tagstart",function(e){for(var a,r=e.attrs,i=e.col+e.tagName.length+1,l=0,d=r.length;d>l;l++)if(a=r[l],"id"===a.name.toLowerCase()&&s.test(a.value)===!1&&t.error(o,e.line,i+a.index,n,a.raw),"class"===a.name.toLowerCase())for(var u,c=a.value.split(/\s+/g),g=0,m=c.length;m>g;g++)u=c[g],u&&s.test(u)===!1&&t.error(o,e.line,i+a.index,n,u)})}}}),HTMLHint.addRule({id:"img-alt-require",description:"Alt of img tag must be set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){if("img"===e.tagName.toLowerCase()){for(var r=e.attrs,n=!1,i=0,s=r.length;s>i;i++)if("alt"===r[i].name.toLowerCase()){n=!0;break}n===!1&&t.error("Alt of img tag must be set value.",e.line,e.col,a,e.raw)}})}}),HTMLHint.addRule({id:"spec-char-escape",description:"Special characters must be escaped.",init:function(e,t){var a=this;e.addListener("text",function(e){for(var r,n=e.raw,i=/[<>]/g;r=i.exec(n);)t.error("Special characters must be escaped : [ "+r[0]+" ].",e.line,e.col+r.index,a,e.raw)})}}),HTMLHint.addRule({id:"style-disabled",description:"Style tag can not be use.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){"style"===e.tagName.toLowerCase()&&t.error("Style tag can not be use.",e.line,e.col,a,e.raw)})}}),HTMLHint.addRule({id:"tag-pair",description:"Tag must be paired.",init:function(e,t){var a=this,r=[],n=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");e.addListener("tagstart",function(e){var t=e.tagName.toLowerCase();void 0!==n[t]||e.close||r.push(t)}),e.addListener("tagend",function(e){for(var n=e.tagName.toLowerCase(),i=r.length-1;i>=0&&r[i]!==n;i--);if(i>=0){for(var s=[],o=r.length-1;o>i;o--)s.push("");s.length>0&&t.error("Tag must be paired, Missing: [ "+s.join("")+" ]",e.line,e.col,a,e.raw),r.length=i}else t.error("Tag must be paired, No start tag.",e.line,e.col,a,e.raw)}),e.addListener("end",function(e){for(var n=[],i=r.length-1;i>=0;i--)n.push("");n.length>0&&t.error("Tag must be paired, Missing: [ "+n.join("")+" ]",e.line,e.col,a,e.raw)})}}),HTMLHint.addRule({id:"tag-self-close",description:"The empty tag must closed by self.",init:function(e,t){var a=this,r=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");e.addListener("tagstart",function(e){var n=e.tagName.toLowerCase();void 0!==r[n]&&(e.close||t.error("The empty tag must closed by self.",e.line,e.col,a,e.raw))})}}),HTMLHint.addRule({id:"tagname-lowercase",description:"Tagname must be lowercase.",init:function(e,t){var a=this;e.addListener("tagstart,tagend",function(e){var r=e.tagName;r!==r.toLowerCase()&&t.error("Tagname [ "+r+" ] must be lower case.",e.line,e.col,a,e.raw)})}}); \ No newline at end of file diff --git a/src/rules/id-class-value.js b/src/rules/id-class-value.js index 99557e419..5b3b604c3 100644 --- a/src/rules/id-class-value.js +++ b/src/rules/id-class-value.js @@ -31,12 +31,14 @@ HTMLHint.addRule({ var regId = rule.regId, message = rule.message; parser.addListener('tagstart', function(event){ - var attrs = event.attrs, attr; + var attrs = event.attrs, + attr, + col = event.col + event.tagName.length + 1; for(var i=0, l1=attrs.length;i]/); - if(match !== null){ + var raw = event.raw, + reSpecChar = /[<>]/g, + match; + while((match = reSpecChar.exec(raw))){ reporter.error('Special characters must be escaped : [ '+match[0]+' ].', event.line, event.col + match.index, self, event.raw); } }); diff --git a/test/rules/attr-value-empty.spec.js b/test/rules/attr-value-empty.spec.js index f256b205a..09c4ccd67 100644 --- a/test/rules/attr-value-empty.spec.js +++ b/test/rules/attr-value-empty.spec.js @@ -2,3 +2,32 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: attr-value-empty', function(){ + + it('Attribute value have no value should result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'attr-value-empty': true}); + expect(messages.length).to.be(1); + expect(messages[0].rule.id).to.be('attr-value-empty'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(7); + }); + + it('Attribute value closed by quote but no value should not result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'attr-value-empty': true}); + expect(messages.length).to.be(0); + }); + + it('Attribute value closed by quote and have value should not result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'attr-value-empty': true}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file diff --git a/test/rules/doctype-first.spec.js b/test/rules/doctype-first.spec.js index f256b205a..355daff08 100644 --- a/test/rules/doctype-first.spec.js +++ b/test/rules/doctype-first.spec.js @@ -2,3 +2,26 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: doctype-first', function(){ + + it('Doctype not be first should result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'doctype-first': true}); + expect(messages.length).to.be(1); + expect(messages[0].rule.id).to.be('doctype-first'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(1); + }); + + it('Doctype be first should not result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'doctype-first': true}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file diff --git a/test/rules/doctype-html5.spec.js b/test/rules/doctype-html5.spec.js index f256b205a..846d57665 100644 --- a/test/rules/doctype-html5.spec.js +++ b/test/rules/doctype-html5.spec.js @@ -2,3 +2,26 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: doctype-html5', function(){ + + it('Doctype not html5 should result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'doctype-html5': true}); + expect(messages.length).to.be(1); + expect(messages[0].rule.id).to.be('doctype-html5'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(1); + }); + + it('Doctype html5 should not result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'doctype-html5': true}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file diff --git a/test/rules/head-script-disabled.spec.js b/test/rules/head-script-disabled.spec.js index f256b205a..e2b40a232 100644 --- a/test/rules/head-script-disabled.spec.js +++ b/test/rules/head-script-disabled.spec.js @@ -2,3 +2,35 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: head-script-disabled', function(){ + + it('External script in head should result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'head-script-disabled': true}); + expect(messages.length).to.be(1); + expect(messages[0].rule.id).to.be('head-script-disabled'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(7); + }); + + it('Internal Script in head should result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'head-script-disabled': true}); + expect(messages.length).to.be(1); + expect(messages[0].rule.id).to.be('head-script-disabled'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(7); + }); + + it('Script in body not result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'head-script-disabled': true}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file diff --git a/test/rules/id-class-value.spec.js b/test/rules/id-class-value.spec.js index f256b205a..f2963f99a 100644 --- a/test/rules/id-class-value.spec.js +++ b/test/rules/id-class-value.spec.js @@ -2,3 +2,65 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: id-class-value', function(){ + + it('Id and class value be not lower case and split by underline should result in an error', function(){ + var code = '
'; + var messages = HTMLHint.verify(code, {'id-class-value': 'underline'}); + expect(messages.length).to.be(2); + expect(messages[0].rule.id).to.be('id-class-value'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(5); + expect(messages[1].rule.id).to.be('id-class-value'); + expect(messages[1].line).to.be(1); + expect(messages[1].col).to.be(17); + }); + + it('Id and class value be lower case and split by underline should not result in an error', function(){ + var code = '
'; + var messages = HTMLHint.verify(code, {'id-class-value': 'underline'}); + expect(messages.length).to.be(0); + }); + + it('Id and class value be not lower case and split by dash should result in an error', function(){ + var code = '
'; + var messages = HTMLHint.verify(code, {'id-class-value': 'dash'}); + expect(messages.length).to.be(2); + expect(messages[0].rule.id).to.be('id-class-value'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(5); + expect(messages[1].rule.id).to.be('id-class-value'); + expect(messages[1].line).to.be(1); + expect(messages[1].col).to.be(17); + }); + + it('Id and class value be lower case and split by dash should not result in an error', function(){ + var code = '
'; + var messages = HTMLHint.verify(code, {'id-class-value': 'dash'}); + expect(messages.length).to.be(0); + }); + + it('Id and class value be not meet hump style should result in an error', function(){ + var code = '
'; + var messages = HTMLHint.verify(code, {'id-class-value': 'hump'}); + expect(messages.length).to.be(2); + expect(messages[0].rule.id).to.be('id-class-value'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(5); + expect(messages[1].rule.id).to.be('id-class-value'); + expect(messages[1].line).to.be(1); + expect(messages[1].col).to.be(17); + }); + + it('Id and class value be meet hump style should not result in an error', function(){ + var code = '
'; + var messages = HTMLHint.verify(code, {'id-class-value': 'hump'}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file diff --git a/test/rules/img-alt-require.spec.js b/test/rules/img-alt-require.spec.js index f256b205a..b14816900 100644 --- a/test/rules/img-alt-require.spec.js +++ b/test/rules/img-alt-require.spec.js @@ -2,3 +2,32 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: img-alt-require', function(){ + + it('Img tag have not alt attr should result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'img-alt-require': true}); + expect(messages.length).to.be(1); + expect(messages[0].rule.id).to.be('img-alt-require'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(1); + }); + + it('Img tag have alt attr should not result in an error', function(){ + var code = 'test'; + var messages = HTMLHint.verify(code, {'img-alt-require': true}); + expect(messages.length).to.be(0); + }); + + it('Img tag have empty alt attr should not result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'img-alt-require': true}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file diff --git a/test/rules/spec-char-escape.spec.js b/test/rules/spec-char-escape.spec.js index f256b205a..e07dbfbd7 100644 --- a/test/rules/spec-char-escape.spec.js +++ b/test/rules/spec-char-escape.spec.js @@ -2,3 +2,29 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: spec-char-escape', function(){ + + it('Special characters: <> should result in an error', function(){ + var code = '

aaa>bbb< ccc

'; + var messages = HTMLHint.verify(code, {'spec-char-escape': true}); + expect(messages.length).to.be(2); + expect(messages[0].rule.id).to.be('spec-char-escape'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(7); + expect(messages[1].rule.id).to.be('spec-char-escape'); + expect(messages[1].line).to.be(1); + expect(messages[1].col).to.be(11); + }); + + it('Normal text should not result in an error', function(){ + var code = '

abc

'; + var messages = HTMLHint.verify(code, {'spec-char-escape': true}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file diff --git a/test/rules/style-disabled.spec.js b/test/rules/style-disabled.spec.js index f256b205a..3844cf385 100644 --- a/test/rules/style-disabled.spec.js +++ b/test/rules/style-disabled.spec.js @@ -2,3 +2,26 @@ * Copyright (c) 2013, Yanis Wang * MIT Licensed */ + +var expect = require("expect.js"); + +var HTMLHint = require("../../index").HTMLHint; + +describe('Rules: style-disabled', function(){ + + it('Style tag should result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'style-disabled': true}); + expect(messages.length).to.be(1); + expect(messages[0].rule.id).to.be('style-disabled'); + expect(messages[0].line).to.be(1); + expect(messages[0].col).to.be(7); + }); + + it('Stylesheet link should not result in an error', function(){ + var code = ''; + var messages = HTMLHint.verify(code, {'style-disabled': true}); + expect(messages.length).to.be(0); + }); + +}); \ No newline at end of file