diff --git a/CHANGE.md b/CHANGE.md index 0d8f0e2a6..9a82083f9 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,6 +1,12 @@ HTMLHint change log ==================== +## ver 0.9.7 (2015-3-7) + +fix: + +1. fix 'No such file' issue on mac + ## ver 0.9.6 (2014-6-18) add: diff --git a/Gruntfile.js b/Gruntfile.js index a418507c9..78121c7a4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -29,7 +29,7 @@ module.exports = function(grunt) { concat: { htmlhint: { src: ['src/core.js', 'src/reporter.js', 'src/htmlparser.js', 'src/rules/*.js'], - dest: 'lib/htmlhint.js' + dest: 'lib/htmlhint.src.js' } }, "mocha-hack": { @@ -56,7 +56,7 @@ module.exports = function(grunt) { uglify: { htmlhint: { options: { - banner: "/*!\r\n * HTMLHint v<%= pkg.version %>\r\n * https://github.com/yaniswang/HTMLHint\r\n *\r\n * (c) 2013 Yanis Wang .\r\n * MIT Licensed\r\n */\n", + banner: "/*!\r\n * HTMLHint v<%= pkg.version %>\r\n * https://github.com/yaniswang/HTMLHint\r\n *\r\n * (c) 2015 Yanis Wang .\r\n * MIT Licensed\r\n */\n", beautify: { ascii_only: true } diff --git a/coverage.html b/coverage.html index 2b0273d64..300e87460 100644 --- a/coverage.html +++ b/coverage.html @@ -338,4 +338,4 @@ code .string { color: #5890AD } code .keyword { color: #8A6343 } code .number { color: #2F6FAD } -

Coverage

98%
416
410
6

htmlhint.js

98%
416
410
6
LineHitsSource
1/**
2 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
3 * MIT Licensed
4 */
51var HTMLHint = (function (undefined) {
6
71 var HTMLHint = {};
8
91 HTMLHint.version = '@VERSION';
10
111 HTMLHint.rules = {};
12
13 //默认配置
141 HTMLHint.defaultRuleset = {
15 'tagname-lowercase': true,
16 'attr-lowercase': true,
17 'attr-value-double-quotes': true,
18 'doctype-first': true,
19 'tag-pair': true,
20 'spec-char-escape': true,
21 'id-unique': true,
22 'src-not-empty': true,
23 'attr-no-duplication': true
24 };
25
261 HTMLHint.addRule = function(rule){
2722 HTMLHint.rules[rule.id] = rule;
28 };
29
301 HTMLHint.verify = function(html, ruleset){
31 // parse inline ruleset
3284 html = html.replace(/^\s*<!--\s*htmlhint\s+([^\r\n]+?)\s*-->/i, function(all, strRuleset){
332 if(ruleset === undefined){
340 ruleset = {};
35 }
362 strRuleset.replace(/(?:^|,)\s*([^:]+)\s*:\s*([^,\s]+)/g, function(all, key, value){
372 if(value === 'false'){
381 value = false;
39 }
401 else if(value === 'true'){
411 value = true;
42 }
432 ruleset[key] = value;
44 });
452 return '';
46 });
47
4884 if(ruleset === undefined || Object.keys(ruleset).length ===0){
493 ruleset = HTMLHint.defaultRuleset;
50 }
51
5284 var parser = new HTMLParser();
5384 var reporter = new HTMLHint.Reporter(html.split(/\r?\n/), ruleset);
54
5584 var rules = HTMLHint.rules,
56 rule;
5784 for (var id in ruleset){
58108 rule = rules[id];
59108 if (rule !== undefined && ruleset[id] !== false){
60105 rule.init(parser, reporter, ruleset[id]);
61 }
62 }
63
6484 parser.parse(html);
65
6684 return reporter.messages;
67 };
68
691 return HTMLHint;
70
71})();
72
731if (typeof exports === 'object' && exports){
741 exports.HTMLHint = HTMLHint;
75}
76/**
77 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
78 * MIT Licensed
79 */
801(function(HTMLHint, undefined){
81
821 var Reporter = function(){
8384 var self = this;
8484 self._init.apply(self,arguments);
85 };
86
871 Reporter.prototype = {
88 _init: function(lines, ruleset){
8984 var self = this;
9084 self.lines = lines;
9184 self.ruleset = ruleset;
9284 self.messages = [];
93 },
94 //错误
95 error: function(message, line, col, rule, raw){
9652 this.report('error', message, line, col, rule, raw);
97 },
98 //警告
99 warn: function(message, line, col, rule, raw){
10049 this.report('warning', message, line, col, rule, raw);
101 },
102 //信息
103 info: function(message, line, col, rule, raw){
1040 this.report('info', message, line, col, rule, raw);
105 },
106 //报告
107 report: function(type, message, line, col, rule, raw){
108101 var self = this;
109101 self.messages.push({
110 type: type,
111 message: message,
112 raw: raw,
113 evidence: self.lines[line-1],
114 line: line,
115 col: col,
116 rule: {
117 id: rule.id,
118 description: rule.description,
119 link: 'https://github.com/yaniswang/HTMLHint/wiki/' + rule.id
120 }
121 });
122 }
123 };
124
1251 HTMLHint.Reporter = Reporter;
126
127})(HTMLHint);
128/**
129 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
130 * MIT Licensed
131 */
1321var HTMLParser = (function(undefined){
133
1341 var HTMLParser = function(){
135109 var self = this;
136109 self._init.apply(self,arguments);
137 };
138
1391 HTMLParser.prototype = {
140 _init: function(){
141109 var self = this;
142109 self._listeners = {};
143109 self._mapCdataTags = self.makeMap("script,style");
144109 self._arrBlocks = [];
145 },
146
147 makeMap: function(str){
148117 var obj = {}, items = str.split(",");
149117 for ( var i = 0; i < items.length; i++ ){
150330 obj[ items[i] ] = true;
151 }
152117 return obj;
153 },
154
155 // parse html code
156 parse: function(html){
157
158109 var self = this,
159 mapCdataTags = self._mapCdataTags;
160
161109 var regTag=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"']+))?)*?)\s*(\/?))>/g,
162 regAttr = /\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"']+)))?/g,
163 regLine = /\r?\n/g;
164
165109 var match, matchIndex, lastIndex = 0, tagName, arrAttrs, tagCDATA, attrsCDATA, arrCDATA, lastCDATAIndex = 0, text;
166109 var lastLineIndex = 0, line = 1;
167109 var arrBlocks = self._arrBlocks;
168
169109 self.fire('start', {
170 pos: 0,
171 line: 1,
172 col: 1
173 });
174
175109 while((match = regTag.exec(html))){
176260 matchIndex = match.index;
177260 if(matchIndex > lastIndex){//保存前面的文本或者CDATA
17878 text = html.substring(lastIndex, matchIndex);
17978 if(tagCDATA){
18010 arrCDATA.push(text);
181 }
182 else{//文本
18368 saveBlock('text', text, lastIndex);
184 }
185 }
186260 lastIndex = regTag.lastIndex;
187
188260 if((tagName = match[1])){
18991 if(tagCDATA && tagName === tagCDATA){//结束标签前输出CDATA
19015 text = arrCDATA.join('');
19115 saveBlock('cdata', text, lastCDATAIndex, {
192 'tagName': tagCDATA,
193 'attrs': attrsCDATA
194 });
19515 tagCDATA = null;
19615 attrsCDATA = null;
19715 arrCDATA = null;
198 }
19991 if(!tagCDATA){
200 //标签结束
20190 saveBlock('tagend', match[0], matchIndex, {
202 'tagName': tagName
203 });
20490 continue;
205 }
206 }
207
208170 if(tagCDATA){
2091 arrCDATA.push(match[0]);
210 }
211 else{
212169 if((tagName = match[4])){//标签开始
213156 arrAttrs = [];
214156 var attrs = match[5],
215 attrMatch,
216 attrMatchCount = 0;
217156 while((attrMatch = regAttr.exec(attrs))){
218152 var name = attrMatch[1],
219 quote = attrMatch[2] ? attrMatch[2] :
220 attrMatch[4] ? attrMatch[4] : '',
221 value = attrMatch[3] ? attrMatch[3] :
222 attrMatch[5] ? attrMatch[5] :
223 attrMatch[6] ? attrMatch[6] : '';
224152 arrAttrs.push({'name': name, 'value': value, 'quote': quote, 'index': attrMatch.index, 'raw': attrMatch[0]});
225152 attrMatchCount += attrMatch[0].length;
226 }
227156 if(attrMatchCount === attrs.length){
228156 saveBlock('tagstart', match[0], matchIndex, {
229 'tagName': tagName,
230 'attrs': arrAttrs,
231 'close': match[6]
232 });
233156 if(mapCdataTags[tagName]){
23415 tagCDATA = tagName;
23515 attrsCDATA = arrAttrs.concat();
23615 arrCDATA = [];
23715 lastCDATAIndex = lastIndex;
238 }
239 }
240 else{//如果出现漏匹配,则把当前内容匹配为text
2410 saveBlock('text', match[0], matchIndex);
242 }
243 }
24413 else if(match[2] || match[3]){//注释标签
24513 saveBlock('comment', match[0], matchIndex, {
246 'content': match[2] || match[3],
247 'long': match[2]?true:false
248 });
249 }
250 }
251 }
252
253109 if(html.length > lastIndex){
254 //结尾文本
25513 text = html.substring(lastIndex, html.length);
25613 saveBlock('text', text, lastIndex);
257 }
258
259109 self.fire('end', {
260 pos: lastIndex,
261 line: line,
262 col: lastIndex - lastLineIndex + 1
263 });
264
265 //存储区块
266109 function saveBlock(type, raw, pos, data){
267355 var col = pos - lastLineIndex + 1;
268355 if(data === undefined){
26981 data = {};
270 }
271355 data.raw = raw;
272355 data.pos = pos;
273355 data.line = line;
274355 data.col = col;
275355 arrBlocks.push(data);
276355 self.fire(type, data);
277355 var lineMatch;
278355 while((lineMatch = regLine.exec(raw))){
27922 line ++;
28022 lastLineIndex = pos + regLine.lastIndex;
281 }
282 }
283
284 },
285
286 // add event
287 addListener: function(types, listener){
288147 var _listeners = this._listeners;
289147 var arrTypes = types.split(/[,\s]/), type;
290147 for(var i=0, l = arrTypes.length;i<l;i++){
291152 type = arrTypes[i];
292152 if (_listeners[type] === undefined){
293131 _listeners[type] = [];
294 }
295152 _listeners[type].push(listener);
296 }
297 },
298
299 // fire event
300 fire: function(type, data){
301573 if (data === undefined){
3020 data = {};
303 }
304573 data.type = type;
305573 var self = this,
306 listeners = [],
307 listenersType = self._listeners[type],
308 listenersAll = self._listeners['all'];
309573 if (listenersType !== undefined){
310164 listeners = listeners.concat(listenersType);
311 }
312573 if (listenersAll !== undefined){
313137 listeners = listeners.concat(listenersAll);
314 }
315573 for (var i = 0, l = listeners.length; i < l; i++){
316328 listeners[i].call(self, data);
317 }
318 },
319
320 // remove event
321 removeListener: function(type, listener){
32215 var listenersType = this._listeners[type];
32315 if(listenersType !== undefined){
32413 for (var i = 0, l = listenersType.length; i < l; i++){
32510 if (listenersType[i] === listener){
32610 listenersType.splice(i, 1);
32710 break;
328 }
329 }
330 }
331 },
332
333 //fix pos if event.raw have \n
334 fixPos: function(event, index){
3358 var text = event.raw.substr(0, index);
3368 var arrLines = text.split(/\r?\n/),
337 lineCount = arrLines.length - 1,
338 line = event.line, col;
3398 if(lineCount > 0){
3402 line += lineCount;
3412 col = arrLines[lineCount].length + 1;
342 }
343 else{
3446 col = event.col + index;
345 }
3468 return {
347 line: line,
348 col: col
349 };
350 },
351
352 // covert array type of attrs to map
353 getMapAttrs: function(arrAttrs){
3546 var mapAttrs = {},
355 attr;
3566 for(var i=0,l=arrAttrs.length;i<l;i++){
3576 attr = arrAttrs[i];
3586 mapAttrs[attr.name] = attr.value;
359 }
3606 return mapAttrs;
361 }
362 };
363
3641 return HTMLParser;
365
366})();
367
3681if (typeof exports === 'object' && exports){
3691 exports.HTMLParser = HTMLParser;
370}
371/**
372 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
373 * MIT Licensed
374 */
3751HTMLHint.addRule({
376 id: 'attr-lowercase',
377 description: 'Attribute name must be lowercase.',
378 init: function(parser, reporter){
3795 var self = this;
3805 parser.addListener('tagstart', function(event){
3819 var attrs = event.attrs,
382 attr,
383 col = event.col + event.tagName.length + 1;
3849 for(var i=0, l=attrs.length;i<l;i++){
38515 attr = attrs[i];
38615 var attrName = attr.name;
38715 if(attrName !== attrName.toLowerCase()){
3884 reporter.error('Attribute name [ '+attrName+' ] must be lower case.', event.line, col + attr.index, self, attr.raw);
389 }
390 }
391 });
392 }
393});
394/**
395 * Copyright (c) 2014, Yanis Wang <yanis.wang@gmail.com>
396 * MIT Licensed
397 */
3981HTMLHint.addRule({
399 id: 'attr-no-duplication',
400 description: 'Attribute name can not been duplication.',
401 init: function(parser, reporter){
4025 var self = this;
4035 parser.addListener('tagstart', function(event){
4049 var attrs = event.attrs;
4059 var attr;
4069 var attrName;
4079 var col = event.col + event.tagName.length + 1;
408
4099 var mapAttrName = {};
4109 for(var i=0, l=attrs.length;i<l;i++){
41116 attr = attrs[i];
41216 attrName = attr.name;
41316 if(mapAttrName[attrName] === true){
4143 reporter.error('The name of attribute [ '+attr.name+' ] been duplication.', event.line, col + attr.index, self, attr.raw);
415 }
41616 mapAttrName[attrName] = true;
417 }
418 });
419 }
420});
421/**
422 * Copyright (c) 2014, Yanis Wang <yanis.wang@gmail.com>
423 * MIT Licensed
424 */
4251HTMLHint.addRule({
426 id: 'attr-unsafe-chars',
427 description: 'Attribute value cant not use unsafe chars.',
428 init: function(parser, reporter){
4292 var self = this;
4302 parser.addListener('tagstart', function(event){
4312 var attrs = event.attrs,
432 attr,
433 col = event.col + event.tagName.length + 1;
4342 var regUnsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
4352 for(var i=0, l=attrs.length;i<l;i++){
4362 attr = attrs[i];
4372 if(regUnsafe.test(attr.value) === true){
4381 reporter.warn('The value of attribute [ '+attr.name+' ] cant not use unsafe chars.', event.line, col + attr.index, self, attr.raw);
439 }
440 }
441 });
442 }
443});
444/**
445 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
446 * MIT Licensed
447 */
4481HTMLHint.addRule({
449 id: 'attr-value-double-quotes',
450 description: 'Attribute value must closed by double quotes.',
451 init: function(parser, reporter){
4525 var self = this;
4535 parser.addListener('tagstart', function(event){
4549 var attrs = event.attrs,
455 attr,
456 col = event.col + event.tagName.length + 1;
4579 for(var i=0, l=attrs.length;i<l;i++){
45819 attr = attrs[i];
45919 if((attr.value !== '' && attr.quote !== '"') ||
460 (attr.value === '' && attr.quote === "'")){
4615 reporter.error('The value of attribute [ '+attr.name+' ] must closed by double quotes.', event.line, col + attr.index, self, attr.raw);
462 }
463 }
464 });
465 }
466});
467/**
468 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
469 * MIT Licensed
470 */
4711HTMLHint.addRule({
472 id: 'attr-value-not-empty',
473 description: 'Attribute must set value.',
474 init: function(parser, reporter){
4753 var self = this;
4763 parser.addListener('tagstart', function(event){
4773 var attrs = event.attrs,
478 attr,
479 col = event.col + event.tagName.length + 1;
4803 for(var i=0, l=attrs.length;i<l;i++){
4813 attr = attrs[i];
4823 if(attr.quote === '' && attr.value === ''){
4831 reporter.warn('The attribute [ '+attr.name+' ] must set value.', event.line, col + attr.index, self, attr.raw);
484 }
485 }
486 });
487 }
488});
489/**
490 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
491 * MIT Licensed
492 */
4931HTMLHint.addRule({
494 id: 'csslint',
495 description: 'Scan css with csslint.',
496 init: function(parser, reporter, options){
4971 var self = this;
4981 parser.addListener('cdata', function(event){
4991 if(event.tagName.toLowerCase() === 'style'){
500
5011 var cssVerify;
502
5031 if(typeof exports === 'object' && require){
5041 cssVerify = require("csslint").CSSLint.verify;
505 }
506 else{
5070 cssVerify = CSSLint.verify;
508 }
509
5101 if(options !== undefined){
5111 var styleLine = event.line - 1,
512 styleCol = event.col - 1;
5131 try{
5141 var messages = cssVerify(event.raw, options).messages;
5151 messages.forEach(function(error){
5162 var line = error.line;
5172 reporter[error.type==='warning'?'warn':'error']('['+error.rule.id+'] '+error.message, styleLine + line, (line === 1 ? styleCol : 0) + error.col, self, error.evidence);
518 });
519 }
520 catch(e){}
521 }
522
523 }
524 });
525 }
526});
527/**
528 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
529 * MIT Licensed
530 */
5311HTMLHint.addRule({
532 id: 'doctype-first',
533 description: 'Doctype must be first.',
534 init: function(parser, reporter){
5355 var self = this;
5365 var allEvent = function(event){
53710 if(event.type === 'start' || (event.type === 'text' && /^\s*$/.test(event.raw))){
5385 return;
539 }
5405 if((event.type !== 'comment' && event.long === false) || /^DOCTYPE\s+/i.test(event.content) === false){
5414 reporter.error('Doctype must be first.', event.line, event.col, self, event.raw);
542 }
5435 parser.removeListener('all', allEvent);
544 };
5455 parser.addListener('all', allEvent);
546 }
547});
548/**
549 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
550 * MIT Licensed
551 */
5521HTMLHint.addRule({
553 id: 'doctype-html5',
554 description: 'Doctype must be html5.',
555 init: function(parser, reporter){
5562 var self = this;
5572 function onComment(event){
5589 if(event.long === false && event.content.toLowerCase() !== 'doctype html'){
5591 reporter.warn('Doctype must be html5.', event.line, event.col, self, event.raw);
560 }
561 }
5622 function onTagStart(){
5632 parser.removeListener('comment', onComment);
5642 parser.removeListener('tagstart', onTagStart);
565 }
5662 parser.addListener('all', onComment);
5672 parser.addListener('tagstart', onTagStart);
568 }
569});
570/**
571 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
572 * MIT Licensed
573 */
5741HTMLHint.addRule({
575 id: 'head-script-disabled',
576 description: 'The script tag can not be used in head.',
577 init: function(parser, reporter){
5783 var self = this;
5793 function onTagStart(event){
5805 if(event.tagName.toLowerCase() === 'script'){
5812 reporter.warn('The script tag can not be used in head.', event.line, event.col, self, event.raw);
582 }
583 }
5843 function onTagEnd(event){
5857 if(event.tagName.toLowerCase() === 'head'){
5863 parser.removeListener('tagstart', onTagStart);
5873 parser.removeListener('tagstart', onTagEnd);
588 }
589 }
5903 parser.addListener('tagstart', onTagStart);
5913 parser.addListener('tagend', onTagEnd);
592 }
593});
594/**
595 * Copyright (c) 2014, Yanis Wang <yanis.wang@gmail.com>
596 * MIT Licensed
597 */
5981HTMLHint.addRule({
599 id: 'href-abs-or-rel',
600 description: 'Href must be absolute or relative.',
601 init: function(parser, reporter, options){
6024 var self = this;
603
6044 var hrefMode = options === 'abs' ? 'absolute' : 'relative';
605
6064 parser.addListener('tagstart', function(event){
60716 var attrs = event.attrs;
60816 var attr;
60916 var col = event.col + event.tagName.length + 1;
610
61116 for(var i=0, l=attrs.length;i<l;i++){
61216 attr = attrs[i];
61316 if(attr.name === 'href'){
61416 if((hrefMode === 'absolute' && /^\w+?:/.test(attr.value) === false) ||
615 (hrefMode === 'relative' && /^https?:\/\//.test(attr.value) === true)){
6164 reporter.warn('The value of href [ '+attr.value+' ] must be '+hrefMode+'.', event.line, col + attr.index, self, attr.raw);
617 }
61816 break;
619 }
620 }
621 });
622 }
623});
624/**
625 * Copyright (c) 2014, Yanis Wang <yanis.wang@gmail.com>
626 * MIT Licensed
627 */
6281HTMLHint.addRule({
629 id: 'id-class-ad-disabled',
630 description: 'Id and class can not use ad keyword, it will blocked by adblock software.',
631 init: function(parser, reporter){
63217 var self = this;
63317 parser.addListener('tagstart', function(event){
63417 var attrs = event.attrs;
63517 var attr;
63617 var attrName;
63717 var col = event.col + event.tagName.length + 1;
638
63917 for(var i=0, l=attrs.length;i<l;i++){
64020 attr = attrs[i];
64120 attrName = attr.name;
64220 if(/^(id|class)$/i.test(attrName)){
64320 if(/(^|[-\_])ad([-\_]|$)/i.test(attr.value)){
64414 reporter.warn('The value of '+attrName+' can not use ad keyword.', event.line, col + attr.index, self, attr.raw);
645 }
646 }
647 }
648 });
649 }
650});
651/**
652 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
653 * MIT Licensed
654 */
6551HTMLHint.addRule({
656 id: 'id-class-value',
657 description: 'Id and class value must meet some rules.',
658 init: function(parser, reporter, options){
6598 var self = this;
6608 var arrRules = {
661 'underline': {
662 'regId': /^[a-z\d]+(_[a-z\d]+)*$/,
663 'message': 'Id and class value must lower case and split by underline.'
664 },
665 'dash': {
666 'regId': /^[a-z\d]+(-[a-z\d]+)*$/,
667 'message': 'Id and class value must lower case and split by dash.'
668 },
669 'hump': {
670 'regId': /^[a-z][a-zA-Z\d]*([A-Z][a-zA-Z\d]*)*$/,
671 'message': 'Id and class value must meet hump style.'
672 }
673 }, rule;
6748 if(typeof options === 'string'){
6756 rule = arrRules[options];
676 }
677 else{
6782 rule = options;
679 }
6808 if(rule && rule.regId){
6818 var regId = rule.regId,
682 message = rule.message;
6838 parser.addListener('tagstart', function(event){
6848 var attrs = event.attrs,
685 attr,
686 col = event.col + event.tagName.length + 1;
6878 for(var i=0, l1=attrs.length;i<l1;i++){
68816 attr = attrs[i];
68916 if(attr.name.toLowerCase() === 'id'){
6908 if(regId.test(attr.value) === false){
6914 reporter.warn(message, event.line, col + attr.index, self, attr.raw);
692 }
693 }
69416 if(attr.name.toLowerCase() === 'class'){
6958 var arrClass = attr.value.split(/\s+/g), classValue;
6968 for(var j=0, l2=arrClass.length;j<l2;j++){
6978 classValue = arrClass[j];
6988 if(classValue && regId.test(classValue) === false){
6994 reporter.warn(message, event.line, col + attr.index, self, classValue);
700 }
701 }
702 }
703 }
704 });
705 }
706 }
707});
708/**
709 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
710 * MIT Licensed
711 */
7121HTMLHint.addRule({
713 id: 'id-unique',
714 description: 'Id must be unique.',
715 init: function(parser, reporter){
7165 var self = this;
7175 var mapIdCount = {};
7185 parser.addListener('tagstart', function(event){
71911 var attrs = event.attrs,
720 attr,
721 id,
722 col = event.col + event.tagName.length + 1;
72311 for(var i=0, l=attrs.length;i<l;i++){
72417 attr = attrs[i];
72517 if(attr.name.toLowerCase() === 'id'){
7268 id = attr.value;
7278 if(id){
7288 if(mapIdCount[id] === undefined){
7295 mapIdCount[id] = 1;
730 }
731 else{
7323 mapIdCount[id] ++;
733 }
7348 if(mapIdCount[id] > 1){
7353 reporter.error('Id redefinition of [ '+id+' ].', event.line, col + attr.index, self, attr.raw);
736 }
737 }
7388 break;
739 }
740 }
741 });
742 }
743});
744/**
745 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
746 * MIT Licensed
747 */
7481HTMLHint.addRule({
749 id: 'img-alt-require',
750 description: 'Alt of img tag must be set value.',
751 init: function(parser, reporter){
7524 var self = this;
7534 parser.addListener('tagstart', function(event){
7544 if(event.tagName.toLowerCase() === 'img'){
7554 var attrs = event.attrs;
7564 var haveAlt = false;
7574 for(var i=0, l=attrs.length;i<l;i++){
7589 if(attrs[i].name.toLowerCase() === 'alt'){
7592 haveAlt = true;
7602 break;
761 }
762 }
7634 if(haveAlt === false){
7642 reporter.warn('Alt of img tag must be set value.', event.line, event.col, self, event.raw);
765 }
766 }
767 });
768 }
769});
770/**
771 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
772 * MIT Licensed
773 */
7741HTMLHint.addRule({
775 id: 'jshint',
776 description: 'Scan script with jshint.',
777 init: function(parser, reporter, options){
7784 var self = this;
7794 parser.addListener('cdata', function(event){
7804 if(event.tagName.toLowerCase() === 'script'){
781
7824 var mapAttrs = parser.getMapAttrs(event.attrs),
783 type = mapAttrs.type;
784
785 // Only scan internal javascript
7864 if(mapAttrs.src !== undefined || (type && /^(text\/javascript)$/i.test(type) === false)){
7872 return;
788 }
789
7902 var jsVerify;
791
7922 if(typeof exports === 'object' && require){
7932 jsVerify = require('jshint').JSHINT;
794 }
795 else{
7960 jsVerify = JSHINT;
797 }
798
7992 if(options !== undefined){
8002 var styleLine = event.line - 1,
801 styleCol = event.col - 1;
8022 var code = event.raw.replace(/\t/g,' ');
8032 try{
8042 var status = jsVerify(code, options);
8052 if(status === false){
8062 jsVerify.errors.forEach(function(error){
8078 var line = error.line;
8088 reporter.warn(error.reason, styleLine + line, (line === 1 ? styleCol : 0) + error.character, self, error.evidence);
809 });
810 }
811 }
812 catch(e){}
813 }
814
815 }
816 });
817 }
818});
819/**
820 * Copyright (c) 2014, Yanis Wang <yanis.wang@gmail.com>
821 * MIT Licensed
822 */
8231HTMLHint.addRule({
824 id: 'space-tab-mixed-disabled',
825 description: 'Spaces and tabs can not mixed in front of line.',
826 init: function(parser, reporter){
8276 var self = this;
8286 parser.addListener('text', function(event){
82912 var raw = event.raw;
83012 var reMixed = /(^|\r?\n)( +\t|\t+ )/g;
83112 var match;
83212 while((match = reMixed.exec(raw))){
8333 var fixedPos = parser.fixPos(event, match.index + match[1].length);
8343 reporter.warn('Mixed spaces and tabs in front of line.', fixedPos.line, 1, self, event.raw);
835 }
836 });
837 }
838});
839/**
840 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
841 * MIT Licensed
842 */
8431HTMLHint.addRule({
844 id: 'spec-char-escape',
845 description: 'Special characters must be escaped.',
846 init: function(parser, reporter){
8475 var self = this;
8485 parser.addListener('text', function(event){
8495 var raw = event.raw,
850 reSpecChar = /[<>]/g,
851 match;
8525 while((match = reSpecChar.exec(raw))){
8535 var fixedPos = parser.fixPos(event, match.index);
8545 reporter.error('Special characters must be escaped : [ '+match[0]+' ].', fixedPos.line, fixedPos.col, self, event.raw);
855 }
856 });
857 }
858});
859/**
860 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
861 * MIT Licensed
862 */
8631HTMLHint.addRule({
864 id: 'src-not-empty',
865 description: 'Src of img(script,link) must set value.',
866 init: function(parser, reporter){
8676 var self = this;
8686 parser.addListener('tagstart', function(event){
86935 var tagName = event.tagName,
870 attrs = event.attrs,
871 attr,
872 col = event.col + tagName.length + 1;
87335 for(var i=0, l=attrs.length;i<l;i++){
87442 attr = attrs[i];
87542 if(((/^(img|script|embed|bgsound|iframe)$/.test(tagName) === true && attr.name === 'src') ||
876 (tagName === 'link' && attr.name === 'href') ||
877 (tagName === 'object' && attr.name === 'data')) &&
878 attr.value === ''){
87916 reporter.error('[ '+attr.name + '] of [ '+tagName+' ] must set value.', event.line, col + attr.index, self, attr.raw);
880 }
881 }
882 });
883 }
884});
885/**
886 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
887 * MIT Licensed
888 */
8891HTMLHint.addRule({
890 id: 'style-disabled',
891 description: 'Style tag can not be use.',
892 init: function(parser, reporter){
8932 var self = this;
8942 parser.addListener('tagstart', function(event){
8954 if(event.tagName.toLowerCase() === 'style'){
8961 reporter.warn('Style tag can not be use.', event.line, event.col, self, event.raw);
897 }
898 });
899 }
900});
901/**
902 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
903 * MIT Licensed
904 */
9051HTMLHint.addRule({
906 id: 'tag-pair',
907 description: 'Tag must be paired.',
908 init: function(parser, reporter){
9096 var self = this;
9106 var stack=[],
911 mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01
9126 parser.addListener('tagstart', function(event){
91311 var tagName = event.tagName.toLowerCase();
91411 if (mapEmptyTags[tagName] === undefined && !event.close){
9159 stack.push(tagName);
916 }
917 });
9186 parser.addListener('tagend', function(event){
9195 var tagName = event.tagName.toLowerCase();
920 //向上寻找匹配的开始标签
9215 for(var pos = stack.length-1;pos >= 0; pos--){
9225 if(stack[pos] === tagName){
9234 break;
924 }
925 }
9265 if(pos >= 0){
9274 var arrTags = [];
9284 for(var i=stack.length-1;i>pos;i--){
9291 arrTags.push('</'+stack[i]+'>');
930 }
9314 if(arrTags.length > 0){
9321 reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, event.raw);
933 }
9344 stack.length=pos;
935 }
936 else{
9371 reporter.error('Tag must be paired, No start tag: [ ' + event.raw + ' ]', event.line, event.col, self, event.raw);
938 }
939 });
9406 parser.addListener('end', function(event){
9416 var arrTags = [];
9426 for(var i=stack.length-1;i>=0;i--){
9434 arrTags.push('</'+stack[i]+'>');
944 }
9456 if(arrTags.length > 0){
9464 reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, '');
947 }
948 });
949 }
950});
951/**
952 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
953 * MIT Licensed
954 */
9551HTMLHint.addRule({
956 id: 'tag-self-close',
957 description: 'The empty tag must closed by self.',
958 init: function(parser, reporter){
9592 var self = this;
9602 var mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01
9612 parser.addListener('tagstart', function(event){
9624 var tagName = event.tagName.toLowerCase();
9634 if(mapEmptyTags[tagName] !== undefined){
9644 if(!event.close){
9652 reporter.warn('The empty tag : [ '+tagName+' ] must closed by self.', event.line, event.col, self, event.raw);
966 }
967 }
968 });
969 }
970});
971/**
972 * Copyright (c) 2013, Yanis Wang <yanis.wang@gmail.com>
973 * MIT Licensed
974 */
9751HTMLHint.addRule({
976 id: 'tagname-lowercase',
977 description: 'Tagname must be lowercase.',
978 init: function(parser, reporter){
9795 var self = this;
9805 parser.addListener('tagstart,tagend', function(event){
98117 var tagName = event.tagName;
98217 if(tagName !== tagName.toLowerCase()){
9836 reporter.error('Tagname [ '+tagName+' ] must be lower case.', event.line, event.col, self, event.raw);
984 }
985 });
986 }
987});
\ No newline at end of file +

Coverage

0%
0
0
0
\ No newline at end of file diff --git a/lib/htmlhint.js b/lib/htmlhint.js index 366cf7d55..109c9bcac 100644 --- a/lib/htmlhint.js +++ b/lib/htmlhint.js @@ -1,8 +1,8 @@ /*! - * HTMLHint v0.9.6 + * HTMLHint v0.9.7 * https://github.com/yaniswang/HTMLHint * - * (c) 2013 Yanis Wang . + * (c) 2015 Yanis Wang . * MIT Licensed */ -var HTMLHint=function(e){var t={};return t.version="0.9.6",t.rules={},t.defaultRuleset={"tagname-lowercase":!0,"attr-lowercase":!0,"attr-value-double-quotes":!0,"doctype-first":!0,"tag-pair":!0,"spec-char-escape":!0,"id-unique":!0,"src-not-empty":!0,"attr-no-duplication":!0},t.addRule=function(e){t.rules[e.id]=e},t.verify=function(a,n){a=a.replace(/^\s*/i,function(t,a){return n===e&&(n={}),a.replace(/(?:^|,)\s*([^:]+)\s*:\s*([^,\s]+)/g,function(e,t,a){"false"===a?a=!1:"true"===a&&(a=!0),n[t]=a}),""}),(n===e||0===Object.keys(n).length)&&(n=t.defaultRuleset);var i,r=new HTMLParser,s=new t.Reporter(a.split(/\r?\n/),n),o=t.rules;for(var l in n)i=o[l],i!==e&&n[l]!==!1&&i.init(r,s,n[l]);return r.parse(a),s.messages},t}();"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,n,i){this.report("error",e,t,a,n,i)},warn:function(e,t,a,n,i){this.report("warning",e,t,a,n,i)},info:function(e,t,a,n,i){this.report("info",e,t,a,n,i)},report:function(e,t,a,n,i,r){var s=this;s.messages.push({type:e,message:t,raw:r,evidence:s.lines[a-1],line:a,col:n,rule:{id:i.id,description:i.description,link:"https://github.com/yaniswang/HTMLHint/wiki/"+i.id}})}},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(","),n=0;a.length>n;n++)t[a[n]]=!0;return t},parse:function(t){function a(t,a,n,i){var r=n-w+1;i===e&&(i={}),i.raw=a,i.pos=n,i.line=b,i.col=r,L.push(i),c.fire(t,i);for(var s;s=p.exec(a);)b++,w=n+p.lastIndex}var n,i,r,s,o,l,d,u,c=this,f=c._mapCdataTags,g=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"']+))?)*?)\s*(\/?))>/g,m=/\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"']+)))?/g,p=/\r?\n/g,v=0,h=0,w=0,b=1,L=c._arrBlocks;for(c.fire("start",{pos:0,line:1,col:1});n=g.exec(t);)if(i=n.index,i>v&&(u=t.substring(v,i),o?d.push(u):a("text",u,v)),v=g.lastIndex,!(r=n[1])||(o&&r===o&&(u=d.join(""),a("cdata",u,h,{tagName:o,attrs:l}),o=null,l=null,d=null),o))if(o)d.push(n[0]);else if(r=n[4]){s=[];for(var H,y=n[5],T=0;H=m.exec(y);){var x=H[1],M=H[2]?H[2]:H[4]?H[4]:"",R=H[3]?H[3]:H[5]?H[5]:H[6]?H[6]:"";s.push({name:x,value:R,quote:M,index:H.index,raw:H[0]}),T+=H[0].length}T===y.length?(a("tagstart",n[0],i,{tagName:r,attrs:s,close:n[6]}),f[r]&&(o=r,l=s.concat(),d=[],h=v)):a("text",n[0],i)}else(n[2]||n[3])&&a("comment",n[0],i,{content:n[2]||n[3],"long":n[2]?!0:!1});else a("tagend",n[0],i,{tagName:r});t.length>v&&(u=t.substring(v,t.length),a("text",u,v)),c.fire("end",{pos:v,line:b,col:v-w+1})},addListener:function(t,a){for(var n,i=this._listeners,r=t.split(/[,\s]/),s=0,o=r.length;o>s;s++)n=r[s],i[n]===e&&(i[n]=[]),i[n].push(a)},fire:function(t,a){a===e&&(a={}),a.type=t;var n=this,i=[],r=n._listeners[t],s=n._listeners.all;r!==e&&(i=i.concat(r)),s!==e&&(i=i.concat(s));for(var o=0,l=i.length;l>o;o++)i[o].call(n,a)},removeListener:function(t,a){var n=this._listeners[t];if(n!==e)for(var i=0,r=n.length;r>i;i++)if(n[i]===a){n.splice(i,1);break}},fixPos:function(e,t){var a,n=e.raw.substr(0,t),i=n.split(/\r?\n/),r=i.length-1,s=e.line;return r>0?(s+=r,a=i[r].length+1):a=e.col+t,{line:s,col:a}},getMapAttrs:function(e){for(var t,a={},n=0,i=e.length;i>n;n++)t=e[n],a[t.name]=t.value;return a}},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 n,i=e.attrs,r=e.col+e.tagName.length+1,s=0,o=i.length;o>s;s++){n=i[s];var l=n.name;l!==l.toLowerCase()&&t.error("Attribute name [ "+l+" ] must be lower case.",e.line,r+n.index,a,n.raw)}})}}),HTMLHint.addRule({id:"attr-no-duplication",description:"Attribute name can not been duplication.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i,r=e.attrs,s=e.col+e.tagName.length+1,o={},l=0,d=r.length;d>l;l++)n=r[l],i=n.name,o[i]===!0&&t.error("The name of attribute [ "+n.name+" ] been duplication.",e.line,s+n.index,a,n.raw),o[i]=!0})}}),HTMLHint.addRule({id:"attr-unsafe-chars",description:"Attribute value cant not use unsafe chars.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i=e.attrs,r=e.col+e.tagName.length+1,s=/[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,o=0,l=i.length;l>o;o++)n=i[o],s.test(n.value)===!0&&t.warn("The value of attribute [ "+n.name+" ] cant not use unsafe chars.",e.line,r+n.index,a,n.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 n,i=e.attrs,r=e.col+e.tagName.length+1,s=0,o=i.length;o>s;s++)n=i[s],(""!==n.value&&'"'!==n.quote||""===n.value&&"'"===n.quote)&&t.error("The value of attribute [ "+n.name+" ] must closed by double quotes.",e.line,r+n.index,a,n.raw)})}}),HTMLHint.addRule({id:"attr-value-not-empty",description:"Attribute must set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i=e.attrs,r=e.col+e.tagName.length+1,s=0,o=i.length;o>s;s++)n=i[s],""===n.quote&&""===n.value&&t.warn("The attribute [ "+n.name+" ] must set value.",e.line,r+n.index,a,n.raw)})}}),HTMLHint.addRule({id:"csslint",description:"Scan css with csslint.",init:function(e,t,a){var n=this;e.addListener("cdata",function(e){if("style"===e.tagName.toLowerCase()){var i;if(i="object"==typeof exports&&require?require("csslint").CSSLint.verify:CSSLint.verify,void 0!==a){var r=e.line-1,s=e.col-1;try{var o=i(e.raw,a).messages;o.forEach(function(e){var a=e.line;t["warning"===e.type?"warn":"error"]("["+e.rule.id+"] "+e.message,r+a,(1===a?s:0)+e.col,n,e.evidence)})}catch(l){}}}})}}),HTMLHint.addRule({id:"doctype-first",description:"Doctype must be first.",init:function(e,t){var a=this,n=function(i){"start"===i.type||"text"===i.type&&/^\s*$/.test(i.raw)||(("comment"!==i.type&&i.long===!1||/^DOCTYPE\s+/i.test(i.content)===!1)&&t.error("Doctype must be first.",i.line,i.col,a,i.raw),e.removeListener("all",n))};e.addListener("all",n)}}),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.warn("Doctype must be html5.",e.line,e.col,i,e.raw)}function n(){e.removeListener("comment",a),e.removeListener("tagstart",n)}var i=this;e.addListener("all",a),e.addListener("tagstart",n)}}),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.warn("The script tag can not be used in head.",e.line,e.col,i,e.raw)}function n(t){"head"===t.tagName.toLowerCase()&&(e.removeListener("tagstart",a),e.removeListener("tagstart",n))}var i=this;e.addListener("tagstart",a),e.addListener("tagend",n)}}),HTMLHint.addRule({id:"href-abs-or-rel",description:"Href must be absolute or relative.",init:function(e,t,a){var n=this,i="abs"===a?"absolute":"relative";e.addListener("tagstart",function(e){for(var a,r=e.attrs,s=e.col+e.tagName.length+1,o=0,l=r.length;l>o;o++)if(a=r[o],"href"===a.name){("absolute"===i&&/^\w+?:/.test(a.value)===!1||"relative"===i&&/^https?:\/\//.test(a.value)===!0)&&t.warn("The value of href [ "+a.value+" ] must be "+i+".",e.line,s+a.index,n,a.raw);break}})}}),HTMLHint.addRule({id:"id-class-ad-disabled",description:"Id and class can not use ad keyword, it will blocked by adblock software.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i,r=e.attrs,s=e.col+e.tagName.length+1,o=0,l=r.length;l>o;o++)n=r[o],i=n.name,/^(id|class)$/i.test(i)&&/(^|[-\_])ad([-\_]|$)/i.test(n.value)&&t.warn("The value of "+i+" can not use ad keyword.",e.line,s+n.index,a,n.raw)})}}),HTMLHint.addRule({id:"id-class-value",description:"Id and class value must meet some rules.",init:function(e,t,a){var n,i=this,r={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(n="string"==typeof a?r[a]:a,n&&n.regId){var s=n.regId,o=n.message;e.addListener("tagstart",function(e){for(var a,n=e.attrs,r=e.col+e.tagName.length+1,l=0,d=n.length;d>l;l++)if(a=n[l],"id"===a.name.toLowerCase()&&s.test(a.value)===!1&&t.warn(o,e.line,r+a.index,i,a.raw),"class"===a.name.toLowerCase())for(var u,c=a.value.split(/\s+/g),f=0,g=c.length;g>f;f++)u=c[f],u&&s.test(u)===!1&&t.warn(o,e.line,r+a.index,i,u)})}}}),HTMLHint.addRule({id:"id-unique",description:"Id must be unique.",init:function(e,t){var a=this,n={};e.addListener("tagstart",function(e){for(var i,r,s=e.attrs,o=e.col+e.tagName.length+1,l=0,d=s.length;d>l;l++)if(i=s[l],"id"===i.name.toLowerCase()){r=i.value,r&&(void 0===n[r]?n[r]=1:n[r]++,n[r]>1&&t.error("Id redefinition of [ "+r+" ].",e.line,o+i.index,a,i.raw));break}})}}),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 n=e.attrs,i=!1,r=0,s=n.length;s>r;r++)if("alt"===n[r].name.toLowerCase()){i=!0;break}i===!1&&t.warn("Alt of img tag must be set value.",e.line,e.col,a,e.raw)}})}}),HTMLHint.addRule({id:"jshint",description:"Scan script with jshint.",init:function(e,t,a){var n=this;e.addListener("cdata",function(i){if("script"===i.tagName.toLowerCase()){var r=e.getMapAttrs(i.attrs),s=r.type;if(void 0!==r.src||s&&/^(text\/javascript)$/i.test(s)===!1)return;var o;if(o="object"==typeof exports&&require?require("jshint").JSHINT:JSHINT,void 0!==a){var l=i.line-1,d=i.col-1,u=i.raw.replace(/\t/g," ");try{var c=o(u,a);c===!1&&o.errors.forEach(function(e){var a=e.line;t.warn(e.reason,l+a,(1===a?d:0)+e.character,n,e.evidence)})}catch(f){}}}})}}),HTMLHint.addRule({id:"space-tab-mixed-disabled",description:"Spaces and tabs can not mixed in front of line.",init:function(e,t){var a=this;e.addListener("text",function(n){for(var i,r=n.raw,s=/(^|\r?\n)( +\t|\t+ )/g;i=s.exec(r);){var o=e.fixPos(n,i.index+i[1].length);t.warn("Mixed spaces and tabs in front of line.",o.line,1,a,n.raw)}})}}),HTMLHint.addRule({id:"spec-char-escape",description:"Special characters must be escaped.",init:function(e,t){var a=this;e.addListener("text",function(n){for(var i,r=n.raw,s=/[<>]/g;i=s.exec(r);){var o=e.fixPos(n,i.index);t.error("Special characters must be escaped : [ "+i[0]+" ].",o.line,o.col,a,n.raw)}})}}),HTMLHint.addRule({id:"src-not-empty",description:"Src of img(script,link) must set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i=e.tagName,r=e.attrs,s=e.col+i.length+1,o=0,l=r.length;l>o;o++)n=r[o],(/^(img|script|embed|bgsound|iframe)$/.test(i)===!0&&"src"===n.name||"link"===i&&"href"===n.name||"object"===i&&"data"===n.name)&&""===n.value&&t.error("[ "+n.name+"] of [ "+i+" ] must set value.",e.line,s+n.index,a,n.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.warn("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,n=[],i=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!==i[t]||e.close||n.push(t)}),e.addListener("tagend",function(e){for(var i=e.tagName.toLowerCase(),r=n.length-1;r>=0&&n[r]!==i;r--);if(r>=0){for(var s=[],o=n.length-1;o>r;o--)s.push("");s.length>0&&t.error("Tag must be paired, Missing: [ "+s.join("")+" ]",e.line,e.col,a,e.raw),n.length=r}else t.error("Tag must be paired, No start tag: [ "+e.raw+" ]",e.line,e.col,a,e.raw)}),e.addListener("end",function(e){for(var i=[],r=n.length-1;r>=0;r--)i.push("");i.length>0&&t.error("Tag must be paired, Missing: [ "+i.join("")+" ]",e.line,e.col,a,"")})}}),HTMLHint.addRule({id:"tag-self-close",description:"The empty tag must closed by self.",init:function(e,t){var a=this,n=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");e.addListener("tagstart",function(e){var i=e.tagName.toLowerCase();void 0!==n[i]&&(e.close||t.warn("The empty tag : [ "+i+" ] 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 n=e.tagName;n!==n.toLowerCase()&&t.error("Tagname [ "+n+" ] must be lower case.",e.line,e.col,a,e.raw)})}}); \ No newline at end of file +var HTMLHint=function(e){var t={};return t.version="0.9.7",t.rules={},t.defaultRuleset={"tagname-lowercase":!0,"attr-lowercase":!0,"attr-value-double-quotes":!0,"doctype-first":!0,"tag-pair":!0,"spec-char-escape":!0,"id-unique":!0,"src-not-empty":!0,"attr-no-duplication":!0},t.addRule=function(e){t.rules[e.id]=e},t.verify=function(a,n){a=a.replace(/^\s*/i,function(t,a){return n===e&&(n={}),a.replace(/(?:^|,)\s*([^:]+)\s*:\s*([^,\s]+)/g,function(e,t,a){"false"===a?a=!1:"true"===a&&(a=!0),n[t]=a}),""}),(n===e||0===Object.keys(n).length)&&(n=t.defaultRuleset);var i,r=new HTMLParser,s=new t.Reporter(a.split(/\r?\n/),n),o=t.rules;for(var l in n)i=o[l],i!==e&&n[l]!==!1&&i.init(r,s,n[l]);return r.parse(a),s.messages},t}();"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,n,i){this.report("error",e,t,a,n,i)},warn:function(e,t,a,n,i){this.report("warning",e,t,a,n,i)},info:function(e,t,a,n,i){this.report("info",e,t,a,n,i)},report:function(e,t,a,n,i,r){var s=this;s.messages.push({type:e,message:t,raw:r,evidence:s.lines[a-1],line:a,col:n,rule:{id:i.id,description:i.description,link:"https://github.com/yaniswang/HTMLHint/wiki/"+i.id}})}},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(","),n=0;a.length>n;n++)t[a[n]]=!0;return t},parse:function(t){function a(t,a,n,i){var r=n-w+1;i===e&&(i={}),i.raw=a,i.pos=n,i.line=b,i.col=r,L.push(i),c.fire(t,i);for(var s;s=m.exec(a);)b++,w=n+m.lastIndex}var n,i,r,s,o,l,d,u,c=this,f=c._mapCdataTags,g=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"']+))?)*?)\s*(\/?))>/g,p=/\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"']+)))?/g,m=/\r?\n/g,h=0,v=0,w=0,b=1,L=c._arrBlocks;for(c.fire("start",{pos:0,line:1,col:1});n=g.exec(t);)if(i=n.index,i>h&&(u=t.substring(h,i),o?d.push(u):a("text",u,h)),h=g.lastIndex,!(r=n[1])||(o&&r===o&&(u=d.join(""),a("cdata",u,v,{tagName:o,attrs:l}),o=null,l=null,d=null),o))if(o)d.push(n[0]);else if(r=n[4]){s=[];for(var H,y=n[5],T=0;H=p.exec(y);){var x=H[1],M=H[2]?H[2]:H[4]?H[4]:"",R=H[3]?H[3]:H[5]?H[5]:H[6]?H[6]:"";s.push({name:x,value:R,quote:M,index:H.index,raw:H[0]}),T+=H[0].length}T===y.length?(a("tagstart",n[0],i,{tagName:r,attrs:s,close:n[6]}),f[r]&&(o=r,l=s.concat(),d=[],v=h)):a("text",n[0],i)}else(n[2]||n[3])&&a("comment",n[0],i,{content:n[2]||n[3],"long":n[2]?!0:!1});else a("tagend",n[0],i,{tagName:r});t.length>h&&(u=t.substring(h,t.length),a("text",u,h)),c.fire("end",{pos:h,line:b,col:h-w+1})},addListener:function(t,a){for(var n,i=this._listeners,r=t.split(/[,\s]/),s=0,o=r.length;o>s;s++)n=r[s],i[n]===e&&(i[n]=[]),i[n].push(a)},fire:function(t,a){a===e&&(a={}),a.type=t;var n=this,i=[],r=n._listeners[t],s=n._listeners.all;r!==e&&(i=i.concat(r)),s!==e&&(i=i.concat(s));for(var o=0,l=i.length;l>o;o++)i[o].call(n,a)},removeListener:function(t,a){var n=this._listeners[t];if(n!==e)for(var i=0,r=n.length;r>i;i++)if(n[i]===a){n.splice(i,1);break}},fixPos:function(e,t){var a,n=e.raw.substr(0,t),i=n.split(/\r?\n/),r=i.length-1,s=e.line;return r>0?(s+=r,a=i[r].length+1):a=e.col+t,{line:s,col:a}},getMapAttrs:function(e){for(var t,a={},n=0,i=e.length;i>n;n++)t=e[n],a[t.name]=t.value;return a}},t}();"object"==typeof exports&&exports&&(exports.HTMLParser=HTMLParser),HTMLHint.addRule({id:"alt-require",description:"Alt of img must be present and alt of area[href] and input[type=image] must be set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(n){var i,r=n.tagName.toLowerCase(),s=e.getMapAttrs(n.attrs),o=n.col+r.length+1;"img"!==r||"alt"in s?("area"===r&&"href"in s||"input"===r&&"image"===s.type)&&("alt"in s&&""!==s.alt||(i="area"===r?"area[href]":"input[type=image]",t.warn("Alt of "+i+" must be set value.",n.line,o,a,n.raw))):t.warn("Alt of img tag must be present.",n.line,o,a,n.raw)})}}),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 n,i=e.attrs,r=e.col+e.tagName.length+1,s=0,o=i.length;o>s;s++){n=i[s];var l=n.name;l!==l.toLowerCase()&&t.error("Attribute name [ "+l+" ] must be lower case.",e.line,r+n.index,a,n.raw)}})}}),HTMLHint.addRule({id:"attr-no-duplication",description:"Attribute name can not been duplication.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i,r=e.attrs,s=e.col+e.tagName.length+1,o={},l=0,d=r.length;d>l;l++)n=r[l],i=n.name,o[i]===!0&&t.error("The name of attribute [ "+n.name+" ] been duplication.",e.line,s+n.index,a,n.raw),o[i]=!0})}}),HTMLHint.addRule({id:"attr-unsafe-chars",description:"Attribute value cant not use unsafe chars.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i=e.attrs,r=e.col+e.tagName.length+1,s=/[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,o=0,l=i.length;l>o;o++)n=i[o],s.test(n.value)===!0&&t.warn("The value of attribute [ "+n.name+" ] cant not use unsafe chars.",e.line,r+n.index,a,n.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 n,i=e.attrs,r=e.col+e.tagName.length+1,s=0,o=i.length;o>s;s++)n=i[s],(""!==n.value&&'"'!==n.quote||""===n.value&&"'"===n.quote)&&t.error("The value of attribute [ "+n.name+" ] must closed by double quotes.",e.line,r+n.index,a,n.raw)})}}),HTMLHint.addRule({id:"attr-value-not-empty",description:"Attribute must set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i=e.attrs,r=e.col+e.tagName.length+1,s=0,o=i.length;o>s;s++)n=i[s],""===n.quote&&""===n.value&&t.warn("The attribute [ "+n.name+" ] must set value.",e.line,r+n.index,a,n.raw)})}}),HTMLHint.addRule({id:"csslint",description:"Scan css with csslint.",init:function(e,t,a){var n=this;e.addListener("cdata",function(e){if("style"===e.tagName.toLowerCase()){var i;if(i="object"==typeof exports&&require?require("csslint").CSSLint.verify:CSSLint.verify,void 0!==a){var r=e.line-1,s=e.col-1;try{var o=i(e.raw,a).messages;o.forEach(function(e){var a=e.line;t["warning"===e.type?"warn":"error"]("["+e.rule.id+"] "+e.message,r+a,(1===a?s:0)+e.col,n,e.evidence)})}catch(l){}}}})}}),HTMLHint.addRule({id:"doctype-first",description:"Doctype must be first.",init:function(e,t){var a=this,n=function(i){"start"===i.type||"text"===i.type&&/^\s*$/.test(i.raw)||(("comment"!==i.type&&i.long===!1||/^DOCTYPE\s+/i.test(i.content)===!1)&&t.error("Doctype must be first.",i.line,i.col,a,i.raw),e.removeListener("all",n))};e.addListener("all",n)}}),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.warn("Doctype must be html5.",e.line,e.col,i,e.raw)}function n(){e.removeListener("comment",a),e.removeListener("tagstart",n)}var i=this;e.addListener("all",a),e.addListener("tagstart",n)}}),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.warn("The script tag can not be used in head.",e.line,e.col,i,e.raw)}function n(t){"head"===t.tagName.toLowerCase()&&(e.removeListener("tagstart",a),e.removeListener("tagstart",n))}var i=this;e.addListener("tagstart",a),e.addListener("tagend",n)}}),HTMLHint.addRule({id:"href-abs-or-rel",description:"Href must be absolute or relative.",init:function(e,t,a){var n=this,i="abs"===a?"absolute":"relative";e.addListener("tagstart",function(e){for(var a,r=e.attrs,s=e.col+e.tagName.length+1,o=0,l=r.length;l>o;o++)if(a=r[o],"href"===a.name){("absolute"===i&&/^\w+?:/.test(a.value)===!1||"relative"===i&&/^https?:\/\//.test(a.value)===!0)&&t.warn("The value of href [ "+a.value+" ] must be "+i+".",e.line,s+a.index,n,a.raw);break}})}}),HTMLHint.addRule({id:"id-class-ad-disabled",description:"Id and class can not use ad keyword, it will blocked by adblock software.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i,r=e.attrs,s=e.col+e.tagName.length+1,o=0,l=r.length;l>o;o++)n=r[o],i=n.name,/^(id|class)$/i.test(i)&&/(^|[-\_])ad([-\_]|$)/i.test(n.value)&&t.warn("The value of "+i+" can not use ad keyword.",e.line,s+n.index,a,n.raw)})}}),HTMLHint.addRule({id:"id-class-value",description:"Id and class value must meet some rules.",init:function(e,t,a){var n,i=this,r={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(n="string"==typeof a?r[a]:a,n&&n.regId){var s=n.regId,o=n.message;e.addListener("tagstart",function(e){for(var a,n=e.attrs,r=e.col+e.tagName.length+1,l=0,d=n.length;d>l;l++)if(a=n[l],"id"===a.name.toLowerCase()&&s.test(a.value)===!1&&t.warn(o,e.line,r+a.index,i,a.raw),"class"===a.name.toLowerCase())for(var u,c=a.value.split(/\s+/g),f=0,g=c.length;g>f;f++)u=c[f],u&&s.test(u)===!1&&t.warn(o,e.line,r+a.index,i,u)})}}}),HTMLHint.addRule({id:"id-unique",description:"Id must be unique.",init:function(e,t){var a=this,n={};e.addListener("tagstart",function(e){for(var i,r,s=e.attrs,o=e.col+e.tagName.length+1,l=0,d=s.length;d>l;l++)if(i=s[l],"id"===i.name.toLowerCase()){r=i.value,r&&(void 0===n[r]?n[r]=1:n[r]++,n[r]>1&&t.error("Id redefinition of [ "+r+" ].",e.line,o+i.index,a,i.raw));break}})}}),HTMLHint.addRule({id:"jshint",description:"Scan script with jshint.",init:function(e,t,a){var n=this;e.addListener("cdata",function(i){if("script"===i.tagName.toLowerCase()){var r=e.getMapAttrs(i.attrs),s=r.type;if(void 0!==r.src||s&&/^(text\/javascript)$/i.test(s)===!1)return;var o;if(o="object"==typeof exports&&require?require("jshint").JSHINT:JSHINT,void 0!==a){var l=i.line-1,d=i.col-1,u=i.raw.replace(/\t/g," ");try{var c=o(u,a);c===!1&&o.errors.forEach(function(e){var a=e.line;t.warn(e.reason,l+a,(1===a?d:0)+e.character,n,e.evidence)})}catch(f){}}}})}}),HTMLHint.addRule({id:"space-tab-mixed-disabled",description:"Spaces and tabs can not mixed in front of line.",init:function(e,t){var a=this;e.addListener("text",function(n){for(var i,r=n.raw,s=/(^|\r?\n)( +\t|\t+ )/g;i=s.exec(r);){var o=e.fixPos(n,i.index+i[1].length);t.warn("Mixed spaces and tabs in front of line.",o.line,1,a,n.raw)}})}}),HTMLHint.addRule({id:"spec-char-escape",description:"Special characters must be escaped.",init:function(e,t){var a=this;e.addListener("text",function(n){for(var i,r=n.raw,s=/[<>]/g;i=s.exec(r);){var o=e.fixPos(n,i.index);t.error("Special characters must be escaped : [ "+i[0]+" ].",o.line,o.col,a,n.raw)}})}}),HTMLHint.addRule({id:"src-not-empty",description:"Src of img(script,link) must set value.",init:function(e,t){var a=this;e.addListener("tagstart",function(e){for(var n,i=e.tagName,r=e.attrs,s=e.col+i.length+1,o=0,l=r.length;l>o;o++)n=r[o],(/^(img|script|embed|bgsound|iframe)$/.test(i)===!0&&"src"===n.name||"link"===i&&"href"===n.name||"object"===i&&"data"===n.name)&&""===n.value&&t.error("[ "+n.name+"] of [ "+i+" ] must set value.",e.line,s+n.index,a,n.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.warn("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,n=[],i=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!==i[t]||e.close||n.push(t)}),e.addListener("tagend",function(e){for(var i=e.tagName.toLowerCase(),r=n.length-1;r>=0&&n[r]!==i;r--);if(r>=0){for(var s=[],o=n.length-1;o>r;o--)s.push("");s.length>0&&t.error("Tag must be paired, Missing: [ "+s.join("")+" ]",e.line,e.col,a,e.raw),n.length=r}else t.error("Tag must be paired, No start tag: [ "+e.raw+" ]",e.line,e.col,a,e.raw)}),e.addListener("end",function(e){for(var i=[],r=n.length-1;r>=0;r--)i.push("");i.length>0&&t.error("Tag must be paired, Missing: [ "+i.join("")+" ]",e.line,e.col,a,"")})}}),HTMLHint.addRule({id:"tag-self-close",description:"The empty tag must closed by self.",init:function(e,t){var a=this,n=e.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");e.addListener("tagstart",function(e){var i=e.tagName.toLowerCase();void 0!==n[i]&&(e.close||t.warn("The empty tag : [ "+i+" ] 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 n=e.tagName;n!==n.toLowerCase()&&t.error("Tagname [ "+n+" ] must be lower case.",e.line,e.col,a,e.raw)})}}); \ No newline at end of file diff --git a/lib/htmlhint.src.js b/lib/htmlhint.src.js new file mode 100644 index 000000000..91cc15362 --- /dev/null +++ b/lib/htmlhint.src.js @@ -0,0 +1,990 @@ +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +var HTMLHint = (function (undefined) { + + var HTMLHint = {}; + + HTMLHint.version = '@VERSION'; + + HTMLHint.rules = {}; + + //默认配置 + HTMLHint.defaultRuleset = { + 'tagname-lowercase': true, + 'attr-lowercase': true, + 'attr-value-double-quotes': true, + 'doctype-first': true, + 'tag-pair': true, + 'spec-char-escape': true, + 'id-unique': true, + 'src-not-empty': true, + 'attr-no-duplication': true + }; + + HTMLHint.addRule = function(rule){ + HTMLHint.rules[rule.id] = rule; + }; + + HTMLHint.verify = function(html, ruleset){ + // parse inline ruleset + html = html.replace(/^\s*/i, function(all, strRuleset){ + if(ruleset === undefined){ + ruleset = {}; + } + strRuleset.replace(/(?:^|,)\s*([^:]+)\s*:\s*([^,\s]+)/g, function(all, key, value){ + if(value === 'false'){ + value = false; + } + else if(value === 'true'){ + value = true; + } + ruleset[key] = value; + }); + return ''; + }); + + if(ruleset === undefined || Object.keys(ruleset).length ===0){ + ruleset = HTMLHint.defaultRuleset; + } + + var parser = new HTMLParser(); + var reporter = new HTMLHint.Reporter(html.split(/\r?\n/), ruleset); + + var rules = HTMLHint.rules, + rule; + for (var id in ruleset){ + rule = rules[id]; + if (rule !== undefined && ruleset[id] !== false){ + rule.init(parser, reporter, ruleset[id]); + } + } + + parser.parse(html); + + return reporter.messages; + }; + + return HTMLHint; + +})(); + +if (typeof exports === 'object' && exports){ + exports.HTMLHint = HTMLHint; +} +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +(function(HTMLHint, undefined){ + + var Reporter = function(){ + var self = this; + self._init.apply(self,arguments); + }; + + Reporter.prototype = { + _init: function(lines, ruleset){ + var self = this; + self.lines = lines; + self.ruleset = ruleset; + self.messages = []; + }, + //错误 + error: function(message, line, col, rule, raw){ + this.report('error', message, line, col, rule, raw); + }, + //警告 + warn: function(message, line, col, rule, raw){ + this.report('warning', message, line, col, rule, raw); + }, + //信息 + info: function(message, line, col, rule, raw){ + this.report('info', message, line, col, rule, raw); + }, + //报告 + report: function(type, message, line, col, rule, raw){ + var self = this; + self.messages.push({ + type: type, + message: message, + raw: raw, + evidence: self.lines[line-1], + line: line, + col: col, + rule: { + id: rule.id, + description: rule.description, + link: 'https://github.com/yaniswang/HTMLHint/wiki/' + rule.id + } + }); + } + }; + + HTMLHint.Reporter = Reporter; + +})(HTMLHint); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +var HTMLParser = (function(undefined){ + + var HTMLParser = function(){ + var self = this; + self._init.apply(self,arguments); + }; + + HTMLParser.prototype = { + _init: function(){ + var self = this; + self._listeners = {}; + self._mapCdataTags = self.makeMap("script,style"); + self._arrBlocks = []; + }, + + makeMap: function(str){ + var obj = {}, items = str.split(","); + for ( var i = 0; i < items.length; i++ ){ + obj[ items[i] ] = true; + } + return obj; + }, + + // parse html code + parse: function(html){ + + var self = this, + mapCdataTags = self._mapCdataTags; + + var regTag=/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"']+))?)*?)\s*(\/?))>/g, + regAttr = /\s*([\w\-:]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"']+)))?/g, + regLine = /\r?\n/g; + + var match, matchIndex, lastIndex = 0, tagName, arrAttrs, tagCDATA, attrsCDATA, arrCDATA, lastCDATAIndex = 0, text; + var lastLineIndex = 0, line = 1; + var arrBlocks = self._arrBlocks; + + self.fire('start', { + pos: 0, + line: 1, + col: 1 + }); + + while((match = regTag.exec(html))){ + matchIndex = match.index; + if(matchIndex > lastIndex){//保存前面的文本或者CDATA + text = html.substring(lastIndex, matchIndex); + if(tagCDATA){ + arrCDATA.push(text); + } + else{//文本 + saveBlock('text', text, lastIndex); + } + } + lastIndex = regTag.lastIndex; + + if((tagName = match[1])){ + if(tagCDATA && tagName === tagCDATA){//结束标签前输出CDATA + text = arrCDATA.join(''); + saveBlock('cdata', text, lastCDATAIndex, { + 'tagName': tagCDATA, + 'attrs': attrsCDATA + }); + tagCDATA = null; + attrsCDATA = null; + arrCDATA = null; + } + if(!tagCDATA){ + //标签结束 + saveBlock('tagend', match[0], matchIndex, { + 'tagName': tagName + }); + continue; + } + } + + if(tagCDATA){ + arrCDATA.push(match[0]); + } + else{ + if((tagName = match[4])){//标签开始 + arrAttrs = []; + var attrs = match[5], + attrMatch, + attrMatchCount = 0; + while((attrMatch = regAttr.exec(attrs))){ + var name = attrMatch[1], + quote = attrMatch[2] ? attrMatch[2] : + attrMatch[4] ? attrMatch[4] : '', + value = attrMatch[3] ? attrMatch[3] : + attrMatch[5] ? attrMatch[5] : + attrMatch[6] ? attrMatch[6] : ''; + arrAttrs.push({'name': name, 'value': value, 'quote': quote, 'index': attrMatch.index, 'raw': attrMatch[0]}); + attrMatchCount += attrMatch[0].length; + } + if(attrMatchCount === attrs.length){ + saveBlock('tagstart', match[0], matchIndex, { + 'tagName': tagName, + 'attrs': arrAttrs, + 'close': match[6] + }); + if(mapCdataTags[tagName]){ + tagCDATA = tagName; + attrsCDATA = arrAttrs.concat(); + arrCDATA = []; + lastCDATAIndex = lastIndex; + } + } + else{//如果出现漏匹配,则把当前内容匹配为text + saveBlock('text', match[0], matchIndex); + } + } + else if(match[2] || match[3]){//注释标签 + saveBlock('comment', match[0], matchIndex, { + 'content': match[2] || match[3], + 'long': match[2]?true:false + }); + } + } + } + + if(html.length > lastIndex){ + //结尾文本 + text = html.substring(lastIndex, html.length); + saveBlock('text', text, lastIndex); + } + + self.fire('end', { + pos: lastIndex, + line: line, + col: lastIndex - lastLineIndex + 1 + }); + + //存储区块 + function saveBlock(type, raw, pos, data){ + var col = pos - lastLineIndex + 1; + if(data === undefined){ + data = {}; + } + data.raw = raw; + data.pos = pos; + data.line = line; + data.col = col; + arrBlocks.push(data); + self.fire(type, data); + var lineMatch; + while((lineMatch = regLine.exec(raw))){ + line ++; + lastLineIndex = pos + regLine.lastIndex; + } + } + + }, + + // add event + addListener: function(types, listener){ + var _listeners = this._listeners; + var arrTypes = types.split(/[,\s]/), type; + for(var i=0, l = arrTypes.length;i 0){ + line += lineCount; + col = arrLines[lineCount].length + 1; + } + else{ + col = event.col + index; + } + return { + line: line, + col: col + }; + }, + + // covert array type of attrs to map + getMapAttrs: function(arrAttrs){ + var mapAttrs = {}, + attr; + for(var i=0,l=arrAttrs.length;i + * Copyright (c) 2014, Takeshi Kurosawa + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'alt-require', + description: 'Alt of img must be present and alt of area[href] and input[type=image] must be set value.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var tagName = event.tagName.toLowerCase(), + mapAttrs = parser.getMapAttrs(event.attrs), + col = event.col + tagName.length + 1, + selector; + if(tagName === 'img' && !('alt' in mapAttrs)){ + reporter.warn('Alt of img tag must be present.', event.line, col, self, event.raw); + } + else if((tagName === 'area' && 'href' in mapAttrs) || + (tagName === 'input' && mapAttrs['type'] === 'image')){ + if(!('alt' in mapAttrs) || mapAttrs['alt'] === ''){ + selector = tagName === 'area' ? 'area[href]' : 'input[type=image]'; + reporter.warn('Alt of ' + selector + ' must be set value.', event.line, col, self, event.raw); + } + } + }); + } +}); + +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'attr-lowercase', + description: 'Attribute name must be lowercase.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs, + attr, + col = event.col + event.tagName.length + 1; + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'attr-no-duplication', + description: 'Attribute name can not been duplication.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs; + var attr; + var attrName; + var col = event.col + event.tagName.length + 1; + + var mapAttrName = {}; + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'attr-unsafe-chars', + description: 'Attribute value cant not use unsafe chars.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs, + attr, + col = event.col + event.tagName.length + 1; + var regUnsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'attr-value-double-quotes', + description: 'Attribute value must closed by double quotes.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs, + attr, + col = event.col + event.tagName.length + 1; + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'attr-value-not-empty', + description: 'Attribute must set value.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs, + attr, + col = event.col + event.tagName.length + 1; + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'csslint', + description: 'Scan css with csslint.', + init: function(parser, reporter, options){ + var self = this; + parser.addListener('cdata', function(event){ + if(event.tagName.toLowerCase() === 'style'){ + + var cssVerify; + + if(typeof exports === 'object' && require){ + cssVerify = require("csslint").CSSLint.verify; + } + else{ + cssVerify = CSSLint.verify; + } + + if(options !== undefined){ + var styleLine = event.line - 1, + styleCol = event.col - 1; + try{ + var messages = cssVerify(event.raw, options).messages; + messages.forEach(function(error){ + var line = error.line; + reporter[error.type==='warning'?'warn':'error']('['+error.rule.id+'] '+error.message, styleLine + line, (line === 1 ? styleCol : 0) + error.col, self, error.evidence); + }); + } + catch(e){} + } + + } + }); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'doctype-first', + description: 'Doctype must be first.', + init: function(parser, reporter){ + var self = this; + var allEvent = function(event){ + if(event.type === 'start' || (event.type === 'text' && /^\s*$/.test(event.raw))){ + return; + } + if((event.type !== 'comment' && event.long === false) || /^DOCTYPE\s+/i.test(event.content) === false){ + reporter.error('Doctype must be first.', event.line, event.col, self, event.raw); + } + parser.removeListener('all', allEvent); + }; + parser.addListener('all', allEvent); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'doctype-html5', + description: 'Doctype must be html5.', + init: function(parser, reporter){ + var self = this; + function onComment(event){ + if(event.long === false && event.content.toLowerCase() !== 'doctype html'){ + reporter.warn('Doctype must be html5.', event.line, event.col, self, event.raw); + } + } + function onTagStart(){ + parser.removeListener('comment', onComment); + parser.removeListener('tagstart', onTagStart); + } + parser.addListener('all', onComment); + parser.addListener('tagstart', onTagStart); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'head-script-disabled', + description: 'The script tag can not be used in head.', + init: function(parser, reporter){ + var self = this; + function onTagStart(event){ + if(event.tagName.toLowerCase() === 'script'){ + reporter.warn('The script tag can not be used in head.', event.line, event.col, self, event.raw); + } + } + function onTagEnd(event){ + if(event.tagName.toLowerCase() === 'head'){ + parser.removeListener('tagstart', onTagStart); + parser.removeListener('tagstart', onTagEnd); + } + } + parser.addListener('tagstart', onTagStart); + parser.addListener('tagend', onTagEnd); + } +}); +/** + * Copyright (c) 2014, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'href-abs-or-rel', + description: 'Href must be absolute or relative.', + init: function(parser, reporter, options){ + var self = this; + + var hrefMode = options === 'abs' ? 'absolute' : 'relative'; + + parser.addListener('tagstart', function(event){ + var attrs = event.attrs; + var attr; + var col = event.col + event.tagName.length + 1; + + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'id-class-ad-disabled', + description: 'Id and class can not use ad keyword, it will blocked by adblock software.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs; + var attr; + var attrName; + var col = event.col + event.tagName.length + 1; + + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'id-class-value', + description: 'Id and class value must meet some rules.', + init: function(parser, reporter, options){ + var self = this; + var arrRules = { + '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.' + } + }, rule; + if(typeof options === 'string'){ + rule = arrRules[options]; + } + else{ + rule = options; + } + if(rule && rule.regId){ + var regId = rule.regId, + message = rule.message; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs, + attr, + col = event.col + event.tagName.length + 1; + for(var i=0, l1=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'id-unique', + description: 'Id must be unique.', + init: function(parser, reporter){ + var self = this; + var mapIdCount = {}; + parser.addListener('tagstart', function(event){ + var attrs = event.attrs, + attr, + id, + col = event.col + event.tagName.length + 1; + for(var i=0, l=attrs.length;i 1){ + reporter.error('Id redefinition of [ '+id+' ].', event.line, col + attr.index, self, attr.raw); + } + } + break; + } + } + }); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'jshint', + description: 'Scan script with jshint.', + init: function(parser, reporter, options){ + var self = this; + parser.addListener('cdata', function(event){ + if(event.tagName.toLowerCase() === 'script'){ + + var mapAttrs = parser.getMapAttrs(event.attrs), + type = mapAttrs.type; + + // Only scan internal javascript + if(mapAttrs.src !== undefined || (type && /^(text\/javascript)$/i.test(type) === false)){ + return; + } + + var jsVerify; + + if(typeof exports === 'object' && require){ + jsVerify = require('jshint').JSHINT; + } + else{ + jsVerify = JSHINT; + } + + if(options !== undefined){ + var styleLine = event.line - 1, + styleCol = event.col - 1; + var code = event.raw.replace(/\t/g,' '); + try{ + var status = jsVerify(code, options); + if(status === false){ + jsVerify.errors.forEach(function(error){ + var line = error.line; + reporter.warn(error.reason, styleLine + line, (line === 1 ? styleCol : 0) + error.character, self, error.evidence); + }); + } + } + catch(e){} + } + + } + }); + } +}); +/** + * Copyright (c) 2014, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'space-tab-mixed-disabled', + description: 'Spaces and tabs can not mixed in front of line.', + init: function(parser, reporter){ + var self = this; + parser.addListener('text', function(event){ + var raw = event.raw; + var reMixed = /(^|\r?\n)( +\t|\t+ )/g; + var match; + while((match = reMixed.exec(raw))){ + var fixedPos = parser.fixPos(event, match.index + match[1].length); + reporter.warn('Mixed spaces and tabs in front of line.', fixedPos.line, 1, self, event.raw); + } + }); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'spec-char-escape', + description: 'Special characters must be escaped.', + init: function(parser, reporter){ + var self = this; + parser.addListener('text', function(event){ + var raw = event.raw, + reSpecChar = /[<>]/g, + match; + while((match = reSpecChar.exec(raw))){ + var fixedPos = parser.fixPos(event, match.index); + reporter.error('Special characters must be escaped : [ '+match[0]+' ].', fixedPos.line, fixedPos.col, self, event.raw); + } + }); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'src-not-empty', + description: 'Src of img(script,link) must set value.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + var tagName = event.tagName, + attrs = event.attrs, + attr, + col = event.col + tagName.length + 1; + for(var i=0, l=attrs.length;i + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'style-disabled', + description: 'Style tag can not be use.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart', function(event){ + if(event.tagName.toLowerCase() === 'style'){ + reporter.warn('Style tag can not be use.', event.line, event.col, self, event.raw); + } + }); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'tag-pair', + description: 'Tag must be paired.', + init: function(parser, reporter){ + var self = this; + var stack=[], + mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01 + parser.addListener('tagstart', function(event){ + var tagName = event.tagName.toLowerCase(); + if (mapEmptyTags[tagName] === undefined && !event.close){ + stack.push(tagName); + } + }); + parser.addListener('tagend', function(event){ + var tagName = event.tagName.toLowerCase(); + //向上寻找匹配的开始标签 + for(var pos = stack.length-1;pos >= 0; pos--){ + if(stack[pos] === tagName){ + break; + } + } + if(pos >= 0){ + var arrTags = []; + for(var i=stack.length-1;i>pos;i--){ + arrTags.push(''); + } + if(arrTags.length > 0){ + reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, event.raw); + } + stack.length=pos; + } + else{ + reporter.error('Tag must be paired, No start tag: [ ' + event.raw + ' ]', event.line, event.col, self, event.raw); + } + }); + parser.addListener('end', function(event){ + var arrTags = []; + for(var i=stack.length-1;i>=0;i--){ + arrTags.push(''); + } + if(arrTags.length > 0){ + reporter.error('Tag must be paired, Missing: [ '+ arrTags.join('') + ' ]', event.line, event.col, self, ''); + } + }); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'tag-self-close', + description: 'The empty tag must closed by self.', + init: function(parser, reporter){ + var self = this; + var mapEmptyTags = parser.makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");//HTML 4.01 + parser.addListener('tagstart', function(event){ + var tagName = event.tagName.toLowerCase(); + if(mapEmptyTags[tagName] !== undefined){ + if(!event.close){ + reporter.warn('The empty tag : [ '+tagName+' ] must closed by self.', event.line, event.col, self, event.raw); + } + } + }); + } +}); +/** + * Copyright (c) 2013, Yanis Wang + * MIT Licensed + */ +HTMLHint.addRule({ + id: 'tagname-lowercase', + description: 'Tagname must be lowercase.', + init: function(parser, reporter){ + var self = this; + parser.addListener('tagstart,tagend', function(event){ + var tagName = event.tagName; + if(tagName !== tagName.toLowerCase()){ + reporter.error('Tagname [ '+tagName+' ] must be lower case.', event.line, event.col, self, event.raw); + } + }); + } +}); \ No newline at end of file diff --git a/package.json b/package.json index b2f0d225a..25104080e 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "A Static Code Analysis Tool for HTML", "main": "./index", "dependencies": { - "commander": "1.1.1", - "colors": "0.6.0-1", - "jshint": "1.1.0", + "commander": "2.6.0", + "colors": "1.0.3", + "jshint": "2.6.3", "csslint": "0.10.0" }, "devDependencies": { diff --git a/test/core-spec.js b/test/core-spec.js index 5ee960a68..363425e58 100644 --- a/test/core-spec.js +++ b/test/core-spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../index").HTMLHint; +var HTMLHint = require("../lib/htmlhint.src.js").HTMLHint; describe('Core', function(){ @@ -34,6 +34,7 @@ describe('Core', function(){ var messages = HTMLHint.verify(code, { 'alt-require': false }); + expect(messages.length).to.be(1); expect(messages[0].rule.id).to.be('alt-require'); expect(messages[0].line).to.be(2); diff --git a/test/htmlparser.spec.js b/test/htmlparser.spec.js index be268b56a..11cc2510c 100644 --- a/test/htmlparser.spec.js +++ b/test/htmlparser.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLParser = require("../index").HTMLParser; +var HTMLParser = require("../lib/htmlhint.src.js").HTMLParser; expect.Assertion.prototype.event = function(type, attr){ var self = this, diff --git a/test/rules/alt-require.js b/test/rules/alt-require.js index 175f9078f..05d284a5a 100644 --- a/test/rules/alt-require.js +++ b/test/rules/alt-require.js @@ -6,7 +6,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'alt-require', ruleOptions = {}; diff --git a/test/rules/attr-lowercase.spec.js b/test/rules/attr-lowercase.spec.js index e5fbe1eec..ddf8108c8 100644 --- a/test/rules/attr-lowercase.spec.js +++ b/test/rules/attr-lowercase.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'attr-lowercase', ruleOptions = {}; diff --git a/test/rules/attr-no-duplication.js b/test/rules/attr-no-duplication.js index 590aab13d..187036ccc 100644 --- a/test/rules/attr-no-duplication.js +++ b/test/rules/attr-no-duplication.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'attr-no-duplication', ruleOptions = {}; diff --git a/test/rules/attr-unsafe-chars.js b/test/rules/attr-unsafe-chars.js index 9bb9dc0a2..c7d31b3af 100644 --- a/test/rules/attr-unsafe-chars.js +++ b/test/rules/attr-unsafe-chars.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'attr-unsafe-chars', ruleOptions = {}; diff --git a/test/rules/attr-value-double-quotes.spec.js b/test/rules/attr-value-double-quotes.spec.js index a899398d4..06df327a3 100644 --- a/test/rules/attr-value-double-quotes.spec.js +++ b/test/rules/attr-value-double-quotes.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'attr-value-double-quotes', ruleOptions = {}; diff --git a/test/rules/attr-value-not-empty.spec.js b/test/rules/attr-value-not-empty.spec.js index d01ccf5d3..d7d1e099e 100644 --- a/test/rules/attr-value-not-empty.spec.js +++ b/test/rules/attr-value-not-empty.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'attr-value-not-empty', ruleOptions = {}; diff --git a/test/rules/csslint.js b/test/rules/csslint.js index bf7d4b3a5..59ef5579e 100644 --- a/test/rules/csslint.js +++ b/test/rules/csslint.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'csslint', ruleOptions = {}; diff --git a/test/rules/default.spec.js b/test/rules/default.spec.js index b9208497c..48d528c1f 100644 --- a/test/rules/default.spec.js +++ b/test/rules/default.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; describe('Rules: default', function(){ diff --git a/test/rules/doctype-first.spec.js b/test/rules/doctype-first.spec.js index 4a89ad661..c693ccd59 100644 --- a/test/rules/doctype-first.spec.js +++ b/test/rules/doctype-first.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'doctype-first', ruleOptions = {}; diff --git a/test/rules/doctype-html5.spec.js b/test/rules/doctype-html5.spec.js index 7b3376ea3..ba9d3ccd3 100644 --- a/test/rules/doctype-html5.spec.js +++ b/test/rules/doctype-html5.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'doctype-html5', ruleOptions = {}; diff --git a/test/rules/head-script-disabled.spec.js b/test/rules/head-script-disabled.spec.js index a89c813ab..0f60d8887 100644 --- a/test/rules/head-script-disabled.spec.js +++ b/test/rules/head-script-disabled.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'head-script-disabled', ruleOptions = {}; diff --git a/test/rules/href-abs-or-rel.js b/test/rules/href-abs-or-rel.js index af0bde87f..87b02871b 100644 --- a/test/rules/href-abs-or-rel.js +++ b/test/rules/href-abs-or-rel.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'href-abs-or-rel'; var ruleOptions = {}; diff --git a/test/rules/id-class-ad-disabled.js b/test/rules/id-class-ad-disabled.js index f8e2b5b24..ffae69fb9 100644 --- a/test/rules/id-class-ad-disabled.js +++ b/test/rules/id-class-ad-disabled.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'id-class-ad-disabled', ruleOptions = {}; diff --git a/test/rules/id-class-value.spec.js b/test/rules/id-class-value.spec.js index 0dc494e9e..9b097df42 100644 --- a/test/rules/id-class-value.spec.js +++ b/test/rules/id-class-value.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'id-class-value', ruleOptionsUnderline = {}, ruleOptionsDash = {}, ruleOptionsHump = {}, ruleOptionsReg = {}; diff --git a/test/rules/id-unique.spec.js b/test/rules/id-unique.spec.js index c083e6072..b50982b3a 100644 --- a/test/rules/id-unique.spec.js +++ b/test/rules/id-unique.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'id-unique', ruleOptions = {}; diff --git a/test/rules/jshint.js b/test/rules/jshint.js index ea9a3688c..bf81238e5 100644 --- a/test/rules/jshint.js +++ b/test/rules/jshint.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'jshint', ruleOptions = {}; @@ -23,7 +23,7 @@ describe('Rules: '+ruldId, function(){ expect(messages.length).to.be(4); expect(messages[0].rule.id).to.be(ruldId); expect(messages[0].line).to.be(3); - expect(messages[0].col).to.be(11); + expect(messages[0].col).to.be(3); expect(messages[0].type).to.be('warning'); expect(messages[1].rule.id).to.be(ruldId); expect(messages[1].line).to.be(4); @@ -35,7 +35,7 @@ describe('Rules: '+ruldId, function(){ expect(messages[2].type).to.be('warning'); expect(messages[3].rule.id).to.be(ruldId); expect(messages[3].line).to.be(2); - expect(messages[3].col).to.be(6); + expect(messages[3].col).to.be(5); expect(messages[3].type).to.be('warning'); }); diff --git a/test/rules/space-tab-mixed-disabled.js b/test/rules/space-tab-mixed-disabled.js index 49a08e236..ed10c8eba 100644 --- a/test/rules/space-tab-mixed-disabled.js +++ b/test/rules/space-tab-mixed-disabled.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'space-tab-mixed-disabled', ruleOptions = {}; diff --git a/test/rules/spec-char-escape.spec.js b/test/rules/spec-char-escape.spec.js index 94c913109..88d6e00d1 100644 --- a/test/rules/spec-char-escape.spec.js +++ b/test/rules/spec-char-escape.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'spec-char-escape', ruleOptions = {}; diff --git a/test/rules/src-not-empty.js b/test/rules/src-not-empty.js index d2205d7f7..7af61d4cf 100644 --- a/test/rules/src-not-empty.js +++ b/test/rules/src-not-empty.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'src-not-empty', ruleOptions = {}; diff --git a/test/rules/style-disabled.spec.js b/test/rules/style-disabled.spec.js index 61a23b3cf..de6374d89 100644 --- a/test/rules/style-disabled.spec.js +++ b/test/rules/style-disabled.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'style-disabled', ruleOptions = {}; diff --git a/test/rules/tag-pair.spec.js b/test/rules/tag-pair.spec.js index 4bc41a846..9faab0420 100644 --- a/test/rules/tag-pair.spec.js +++ b/test/rules/tag-pair.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'tag-pair', ruleOptions = {}; diff --git a/test/rules/tag-self-close.spec.js b/test/rules/tag-self-close.spec.js index 37da141bb..9467d866d 100644 --- a/test/rules/tag-self-close.spec.js +++ b/test/rules/tag-self-close.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'tag-self-close', ruleOptions = {}; diff --git a/test/rules/tagname-lowercase.spec.js b/test/rules/tagname-lowercase.spec.js index a3861127d..5e28676df 100644 --- a/test/rules/tagname-lowercase.spec.js +++ b/test/rules/tagname-lowercase.spec.js @@ -5,7 +5,7 @@ var expect = require("expect.js"); -var HTMLHint = require("../../index").HTMLHint; +var HTMLHint = require("../../lib/htmlhint.src.js").HTMLHint; var ruldId = 'tagname-lowercase', ruleOptions = {};