Explorar o código

Offenders layout for DOMqueriesAvoidable, jsErrors and globalVariables

Gaël Métais %!s(int64=10) %!d(string=hai) anos
pai
achega
23dc46d697

+ 12 - 9
front/src/css/rule.css

@@ -82,7 +82,7 @@
   font-size: 3em;
   margin-bottom: 1em;
 }
-.offenders .eltButton {
+.offenders .offenderButton {
   display: inline-block;
   position: relative;
   background: #efe;
@@ -91,16 +91,16 @@
   border-radius: 0.4em;
   z-index: 1;
 }
-.offenders .eltButton.opens {
+.offenders .offenderButton.opens {
   padding-right: 0.75em;
 }
-.offenders .eltButton.opens:after {
+.offenders .offenderButton.opens:after {
   position: relative;
   left: 0.5em;
   content: '\25BC';
   font-size: 0.8em;
 }
-.offenders .eltButton > div {
+.offenders .offenderButton > div {
   display: none;
   position: absolute;
   right: 0;
@@ -110,23 +110,26 @@
   border-bottom-right-radius: 0.4em;
   border-top: 1px solid #999;
 }
-.offenders .eltButton .domTree {
+.offenders .offenderButton .domTree {
   text-align: left;
   white-space: nowrap;
 }
-.offenders .eltButton .domTree > div {
+.offenders .offenderButton .domTree > div {
   margin: 0.5em;
 }
-.offenders .eltButton .domTree > div div {
+.offenders .offenderButton .domTree > div div {
   margin-left: 1em;
 }
-.offenders .eltButton:hover {
+.offenders .offenderButton .backtrace {
+  white-space: nowrap;
+}
+.offenders .offenderButton:hover {
   border-bottom-left-radius: 0;
   border-bottom-right-radius: 0;
   background: #ffe0cc;
   z-index: 2;
 }
-.offenders .eltButton:hover > div {
+.offenders .offenderButton:hover > div {
   display: block;
 }
 .offendersHtml {

+ 5 - 1
front/src/less/rule.less

@@ -91,7 +91,7 @@
 }
 
 .offenders {
-    .eltButton {
+    .offenderButton {
         display: inline-block;
         position: relative;
         background: #efe;
@@ -135,6 +135,10 @@
             }
         }
 
+        .backtrace {
+            white-space: nowrap;
+        }
+
         &:hover {
             border-bottom-left-radius: 0;
             border-bottom-right-radius: 0;

+ 59 - 15
lib/metadata/policies.js

@@ -44,7 +44,7 @@ var policies = {
                     debug('DOMidDuplicated offenders transform function error with "%s"', offender);
                     return offender;
                 }
-                
+
                 return '<b>#' + parts[1] + '</b>: ' + parts[2] + ' occurrences';
             });
         }
@@ -77,15 +77,6 @@ var policies = {
         "isBadThreshold": 1000,
         "isAbnormalThreshold": 2000
     },
-    "DOMqueriesAvoidable": {
-        "tool": "phantomas",
-        "label": "Duplicated DOM queries",
-        "message": "<p>This is the number of queries that could be avoided by removing all duplicated queries.</p><p>Simply save the result of a query in a variable. Ok it is not always simple, especially with third-party scripts, but at least do it with your own code.</p>",
-        "isOkThreshold": 0,
-        "isBadThreshold": 200,
-        "isAbnormalThreshold": 500,
-        "takeOffendersFrom": "DOMqueriesDuplicated"
-    },
     "DOMqueriesWithoutResults": {
         "tool": "phantomas",
         "label": "DOM queries without result",
@@ -102,7 +93,28 @@ var policies = {
                     return offender;
                 }
 
-                return '<b>' + parts[1] + '</b> (in ' + offendersHelpers.domPathToButton(parts[2]) + ') using <b>' + parts[3] + '</b>';
+                return '<b>' + parts[1] + '</b> (in ' + offendersHelpers.domPathToButton(parts[2]) + ') using ' + parts[3];
+            });
+        }
+    },
+    "DOMqueriesAvoidable": {
+        "tool": "phantomas",
+        "label": "Duplicated DOM queries",
+        "message": "<p>This is the number of queries that could be avoided by removing all duplicated queries.</p><p>Simply save the result of a query in a variable. Ok it is not always simple, especially with third-party scripts, but at least do it with your own code.</p>",
+        "isOkThreshold": 0,
+        "isBadThreshold": 200,
+        "isAbnormalThreshold": 500,
+        "takeOffendersFrom": "DOMqueriesDuplicated",
+        "offendersTransformFn": function(offenders) {
+            return offenders.map(function(offender) {
+                var parts = /^.* "(.*)" with (.*) \(in context (.*)\): (.*)\s?queries$/.exec(offender);
+
+                if (!parts) {
+                    debug('DOMqueriesAvoidable offenders transform function error with "%s"', offender);
+                    return offender;
+                }
+
+                return '<b>' + parts[1] + '</b> (in ' + offendersHelpers.domPathToButton(parts[3]) + ') using ' + parts[2] + ': <b>' + parts[4] + ' queries</b>';
             });
         }
     },
@@ -112,7 +124,19 @@ var policies = {
         "message": "<p>Binding too many events has a cost.</p><p>It can be avoided by using \"event delegation\". Instead of binding events on each element one by one, events delegation binds them on the top level document element and uses the bubbling principle. It will imperceptibly slow down the event when it occurs, but the loading of the page will speed-up.</p>",
         "isOkThreshold": 100,
         "isBadThreshold": 800,
-        "isAbnormalThreshold": 1500
+        "isAbnormalThreshold": 1500,
+        "offendersTransformFn": function(offenders) {
+            return offenders.map(function(offender) {
+                var parts = /^"(.*)" bound to "(.*)"$/.exec(offender);
+
+                if (!parts) {
+                    debug('eventsBound offenders transform function error with "%s"', offender);
+                    return offender;
+                }
+
+                return '<b>' + parts[1] + '</b> bound to ' + offendersHelpers.domPathToButton(parts[2]);
+            });
+        }
     },
     "jsErrors": {
         "tool": "phantomas",
@@ -120,7 +144,24 @@ var policies = {
         "message": "<p>Just to let you know there are some errors on the page.</p><p><b>Please note that some errors only occur in the PhantomJS browser, so you might need to double check on other browsers.</b></p>",
         "isOkThreshold": 0,
         "isBadThreshold": 1,
-        "isAbnormalThreshold": 4
+        "isAbnormalThreshold": 4,
+        "offendersTransformFn": function(offenders) {
+            return offenders.map(function(offender) {
+                var parts = /^(.*) - (.*)$/.exec(offender);
+
+                if (!parts) {
+                    debug('jsErrors offenders transform function error with "%s"', offender);
+                    return offender;
+                }
+
+                var backtraceArray = offendersHelpers.backtraceToArray(parts[2]);
+                if (backtraceArray === null) {
+                    return offender;
+                } else {
+                    return '<b>' + parts[1] + '</b> ' + offendersHelpers.backtraceArrayToHtml(backtraceArray);
+                }
+            });
+        }
     },
     "evalCalls": {
         "tool": "phantomas",
@@ -152,7 +193,10 @@ var policies = {
         "message": "<p>It is a bad practice because they clutter up the global namespace. If two scripts use the same variable name in the global scope, it can cause conflicts and it is generally hard to debug.</p><p>Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.</p>",
         "isOkThreshold": 10,
         "isBadThreshold": 50,
-        "isAbnormalThreshold": 200
+        "isAbnormalThreshold": 200,
+        "offendersTransformFn": function(offenders) {
+            return offendersHelpers.sortVarsLikeChromeDevTools(offenders);
+        }
     },
     "jQueryVersion": {
         "label": "jQuery version",
@@ -210,7 +254,7 @@ var policies = {
     "jQueryDifferentVersions": {
         "tool": "phantomas",
         "label": "Several versions loaded",
-        "message": "<p>jQuery is a heavy library. You should <b>never<b> load jQuery more than one on the same page.</p>",
+        "message": "<p>jQuery is a heavy library. You should <b>never</b> load jQuery more than once on the same page.</p>",
         "isOkThreshold": 1,
         "isBadThreshold": 2,
         "isAbnormalThreshold": 2

+ 58 - 10
lib/offendersHelpers.js

@@ -11,7 +11,7 @@ var OffendersHelpers = function() {
 
         function recursiveTreeBuilder(tree, domArray) {
             if (domArray.length > 0) {
-                var currentDomElement = domArray.shift(domArray);
+                var currentDomElement = domArray.shift();
                 if (tree === null) {
                     tree = {};
                 }
@@ -64,37 +64,85 @@ var OffendersHelpers = function() {
         var domTree = this.listOfDomPathsToHTML([domPath]);
 
         if (domArray[0] === 'html') {
-            return '<div class="eltButton htmlButton"><b>html</b></div>';
+            return '<div class="offenderButton"><b>html</b></div>';
         }
         if (domArray[0] === 'body') {
             if (domArray.length === 1) {
-                return '<div class="eltButton bodyButton"><b>body</b></div>';
+                return '<div class="offenderButton"><b>body</b></div>';
             } else {
-                return '<div class="eltButton domButton opens">DOM element <b>' + domArray[domArray.length - 1] + '</b>' + domTree + '</div>';
+                return '<div class="offenderButton opens">DOM element <b>' + domArray[domArray.length - 1] + '</b>' + domTree + '</div>';
             }
         }
         if (domArray[0] === 'head') {
-            return '<div class="eltButton headButton"><b>head</b></div>';
+            return '<div class="offenderButton"><b>head</b></div>';
         }
         if (domArray[0] === '#document') {
-            return '<div class="eltButton documentButton"><b>document</b></div>';
+            return '<div class="offenderButton"><b>document</b></div>';
+        }
+        if (domArray[0] === 'window') {
+            return '<div class="offenderButton"><b>window</b></div>';
         }
         if (domArray[0] === 'DocumentFragment') {
             if (domArray.length === 1) {
-                return '<div class="eltButton fragButton">Fragment</div>';
+                return '<div class="offenderButton">Fragment</div>';
             } else {
-                return '<div class="eltButton fragEltButton opens">Fragment element <b>' + domArray[domArray.length - 1] + '</b>' + domTree + '</div>';
+                return '<div class="offenderButton opens">Fragment element <b>' + domArray[domArray.length - 1] + '</b>' + domTree + '</div>';
             }
         }
         
         // Not attached element, such as just created with document.createElement()
         if (domArray.length === 1) {
-            return '<div class="eltButton aloneButton">Created element <b>' + domPath + '</b></div>';
+            return '<div class="offenderButton">Created element <b>' + domPath + '</b></div>';
         } else {
-            return '<div class="eltButton aloneEltButton opens">Created element <b>' + domArray[domArray.length - 1] + '</b>' + domTree + '</div>';
+            return '<div class="offenderButton opens">Created element <b>' + domArray[domArray.length - 1] + '</b>' + domTree + '</div>';
         }
     };
 
+
+    this.backtraceToArray = function(str) {
+        var traceArray = str.split(/ \/ /);
+
+        if (traceArray) {
+            var results = [];
+            var parts = null;
+
+            for (var i=0 ; i<traceArray.length ; i++) {
+                parts = /^([^ ]+):(\d*)$/.exec(traceArray[i]);
+
+                if (parts) {
+                    results.push({
+                        file: parts[1],
+                        line: parseInt(parts[2], 10)
+                    });
+                } else {
+                    return null;
+                }
+            }
+            return results;
+        } else {
+            return null;
+        }
+    };
+
+    this.backtraceArrayToHtml = function(backtraceArray) {
+        if (backtraceArray.length === 0) {
+            return '<div class="offenderButton">no backtrace</div>';
+        }
+
+        var html = '<div class="offenderButton opens">backtrace<div class="backtrace">';
+        backtraceArray.forEach(function(backtraceObj) {
+            html += '<div><a href="' + backtraceObj.file + '" target="_blank">' + backtraceObj.file + '</a> line ' + backtraceObj.line + '</div>';
+        });
+        return html + '</div></div>';
+    };
+
+
+    this.sortVarsLikeChromeDevTools = function(vars) {
+        return vars.sort(function(a, b) {
+            return (a < b) ? -1 : 1;
+        });
+    };
+
 };
 
 module.exports = new OffendersHelpers();

+ 106 - 9
test/core/offendersHelpersTest.js

@@ -84,47 +84,144 @@ describe('offendersHelpers', function() {
 
         it('should transform html', function() {
             var result = offendersHelpers.domPathToButton('html');
-            result.should.equal('<div class="eltButton htmlButton"><b>html</b></div>');
+            result.should.equal('<div class="offenderButton"><b>html</b></div>');
         });
 
         it('should transform body', function() {
             var result = offendersHelpers.domPathToButton('body');
-            result.should.equal('<div class="eltButton bodyButton"><b>body</b></div>');
+            result.should.equal('<div class="offenderButton"><b>body</b></div>');
         });
 
         it('should transform head', function() {
             var result = offendersHelpers.domPathToButton('head');
-            result.should.equal('<div class="eltButton headButton"><b>head</b></div>');
+            result.should.equal('<div class="offenderButton"><b>head</b></div>');
         });
 
         it('should transform #document', function() {
             var result = offendersHelpers.domPathToButton('#document');
-            result.should.equal('<div class="eltButton documentButton"><b>document</b></div>');
+            result.should.equal('<div class="offenderButton"><b>document</b></div>');
+        });
+
+        it('should transform window', function() {
+            var result = offendersHelpers.domPathToButton('window');
+            result.should.equal('<div class="offenderButton"><b>window</b></div>');
         });
 
         it('should transform a standard in-body element', function() {
             var result = offendersHelpers.domPathToButton('body > div#colorbox > div#cboxContent');
-            result.should.equal('<div class="eltButton domButton opens">DOM element <b>div#cboxContent</b><div class="domTree"><div><span>body</span><div><span>div#colorbox</span><div><span>div#cboxContent</span></div></div></div></div></div>');
+            result.should.equal('<div class="offenderButton opens">DOM element <b>div#cboxContent</b><div class="domTree"><div><span>body</span><div><span>div#colorbox</span><div><span>div#cboxContent</span></div></div></div></div></div>');
         });
 
         it('should transform a domFragment element', function() {
             var result = offendersHelpers.domPathToButton('DocumentFragment');
-            result.should.equal('<div class="eltButton fragButton">Fragment</div>');
+            result.should.equal('<div class="offenderButton">Fragment</div>');
         });
 
         it('should transform a domFragment element', function() {
             var result = offendersHelpers.domPathToButton('DocumentFragment > div#colorbox > div#cboxContent');
-            result.should.equal('<div class="eltButton fragEltButton opens">Fragment element <b>div#cboxContent</b><div class="domTree"><div><span>DocumentFragment</span><div><span>div#colorbox</span><div><span>div#cboxContent</span></div></div></div></div></div>');
+            result.should.equal('<div class="offenderButton opens">Fragment element <b>div#cboxContent</b><div class="domTree"><div><span>DocumentFragment</span><div><span>div#colorbox</span><div><span>div#cboxContent</span></div></div></div></div></div>');
         });
 
         it('should transform an not-attached element', function() {
             var result = offendersHelpers.domPathToButton('div#sizcache');
-            result.should.equal('<div class="eltButton aloneButton">Created element <b>div#sizcache</b></div>');
+            result.should.equal('<div class="offenderButton">Created element <b>div#sizcache</b></div>');
         });
 
         it('should transform an not-attached element path', function() {
             var result = offendersHelpers.domPathToButton('div > div#sizcache');
-            result.should.equal('<div class="eltButton aloneEltButton opens">Created element <b>div#sizcache</b><div class="domTree"><div><span>div</span><div><span>div#sizcache</span></div></div></div></div>');
+            result.should.equal('<div class="offenderButton opens">Created element <b>div#sizcache</b><div class="domTree"><div><span>div</span><div><span>div#sizcache</span></div></div></div></div>');
+        });
+
+    });
+
+    describe('backtraceToArray', function() {
+
+        it('should transform a backtrace into an array', function() {
+            var result = offendersHelpers.backtraceToArray('http://pouet.com/js/jquery.footer-transverse-min-v1.0.20.js:1 / http://pouet.com/js/main.js:1');
+
+            result.should.deep.equal([
+                {
+                    file: 'http://pouet.com/js/jquery.footer-transverse-min-v1.0.20.js',
+                    line: 1
+                },
+                {
+                    file: 'http://pouet.com/js/main.js',
+                    line: 1
+                }
+            ]);
+        });
+
+        it('should return null if it fails', function() {
+            var result = offendersHelpers.backtraceToArray('http://pouet.com/js/jquery.footer-transverse-min-v1.0.20.js:1 /http://pouet.com/js/main.js:1');
+
+            should.equal(result, null);
+        });
+
+    });
+
+    describe('backtraceArrayToHtml', function() {
+
+        it('should create a button from a backtrace array', function() {
+            var result = offendersHelpers.backtraceArrayToHtml([
+                {
+                    file: 'http://pouet.com/js/jquery.footer-transverse-min-v1.0.20.js',
+                    line: 1
+                },
+                {
+                    file: 'http://pouet.com/js/main.js',
+                    line: 1
+                }
+            ]);
+
+            result.should.equal('<div class="offenderButton opens">backtrace<div class="backtrace"><div><a href="http://pouet.com/js/jquery.footer-transverse-min-v1.0.20.js" target="_blank">http://pouet.com/js/jquery.footer-transverse-min-v1.0.20.js</a> line 1</div><div><a href="http://pouet.com/js/main.js" target="_blank">http://pouet.com/js/main.js</a> line 1</div></div></div>');
+        });
+
+        it('should display "no backtrace"', function() {
+            var result = offendersHelpers.backtraceArrayToHtml([]);
+
+            result.should.equal('<div class="offenderButton">no backtrace</div>');
+        });
+
+    });
+
+    describe('sortVarsLikeChromeDevTools', function() {
+
+        it('should sort in the same strange order', function() {
+            var result = offendersHelpers.sortVarsLikeChromeDevTools([
+                'a',
+                'aaa',
+                'a2',
+                'b',
+                'A',
+                'AAA',
+                'B',
+                '_a',
+                '_aaa',
+                '__a',
+                'a_a',
+                'aA',
+                'a__',
+                '$',
+                '$a'
+            ]);
+
+            result.should.deep.equal([
+                '$',
+                '$a',
+                'A',
+                'AAA',
+                'B',
+                '__a',
+                '_a',
+                '_aaa',
+                'a',
+                'a2',
+                'aA',
+                'a__',
+                'a_a',
+                'aaa',
+                'b'
+            ]);
         });
 
     });