Browse Source

Merge pull request #43 from gmetais/phantomas1.9

Phantomas1.9
Gaël Métais 10 years ago
parent
commit
d1f0b2e459

+ 42 - 0
front/src/css/rule.css

@@ -148,3 +148,45 @@
   font-style: italic;
   font-weight: normal;
 }
+.colorPalette {
+  width: 30em;
+  border: 2px solid #000;
+  text-align: left;
+  /* Checkerboard background */
+  background-color: #ddd;
+  background-image: linear-gradient(45deg, #aaaaaa 25%, transparent 25%, transparent 75%, #aaaaaa 75%, #aaaaaa), linear-gradient(45deg, #aaaaaa 25%, transparent 25%, transparent 75%, #aaaaaa 75%, #aaaaaa);
+  background-size: 1em 1em;
+  background-position: 0 0, 0.5em 0.5em;
+}
+.colorPalette > div {
+  display: inline-block;
+  height: 2em;
+  position: relative;
+}
+.colorPalette > div div {
+  display: none;
+  position: absolute;
+  left: 100%;
+  top: 100%;
+  background: #FFF;
+  padding: 0.5em;
+  border: 2px solid #f1c40f;
+  border-radius: 0.5em;
+  white-space: nowrap;
+  z-index: 3;
+  font-weight: bold;
+}
+.colorPalette > div:hover div {
+  display: block;
+}
+.colorPalette > div:hover:after {
+  content: " ";
+  position: absolute;
+  background-color: inherit;
+  left: -0.2em;
+  top: -0.2em;
+  width: 100%;
+  height: 100%;
+  z-index: 2;
+  border: 0.2em solid #f1c40f;
+}

+ 48 - 0
front/src/less/rule.less

@@ -168,4 +168,52 @@
             font-weight: normal;
         }
     }
+}
+
+.colorPalette {
+    width: 30em;
+    border: 2px solid #000;
+    text-align: left;
+
+    /* Checkerboard background */
+    background-color: #ddd;
+    background-image: linear-gradient(45deg, #AAA 25%, transparent 25%, transparent 75%, #AAA 75%, #AAA), linear-gradient(45deg, #AAA 25%, transparent 25%, transparent 75%, #AAA 75%, #AAA);
+    background-size:1em 1em;
+    background-position:0 0, 0.5em 0.5em;
+
+    > div {
+        display: inline-block;
+        height: 2em;
+        position: relative;
+
+        div {
+            display: none;
+            position: absolute;
+            left: 100%;
+            top: 100%;
+            background: #FFF;
+            padding: 0.5em;
+            border: 2px solid #f1c40f;
+            border-radius: 0.5em;
+            white-space: nowrap;
+            z-index: 3;
+            font-weight: bold;
+        }
+
+        &:hover div {
+            display: block;
+        }
+
+        &:hover:after {
+            content: " ";
+            position: absolute;
+            background-color: inherit;
+            left: -0.2em;
+            top: -0.2em;
+            width: 100%;
+            height: 100%;
+            z-index: 2;
+            border: 0.2em solid #f1c40f;
+        }
+    }
 }

+ 8 - 1
front/src/views/rule.html

@@ -128,7 +128,7 @@
                         (<ng-pluralize count="offender.requests" when="{'one':'1 request','other':'{} requests'}"></ng-pluralize>)
                     </div>
 
-                    <div ng-if="policyName === 'globalVariables' || policyName === 'jQueryDifferentVersions'">
+                    <div ng-if="policyName === 'globalVariables' || policyName === 'jQueryVersionsLoaded'">
                         {{offender}}
                     </div>
 
@@ -143,6 +143,13 @@
                 <dom-tree tree="rule.offendersObj.tree"></dom-tree>
             </div>
 
+            <div ng-if="policyName === 'cssColors' && rule.offendersObj.count > 0">
+                <p>This is the color palette, sized by total occurrences:</p>
+                <div class="colorPalette">
+                    <div ng-repeat="offender in rule.offendersObj.palette" style="background-color: {{offender.color}}; width: {{offender.occurrences * 100 / rule.offendersObj.palette[0].occurrences}}%"><div>{{offender.color}} ({{offender.occurrences}} times)</div></div>
+                </div>
+            </div>
+
         </div>
 
     </div>

+ 48 - 2
lib/metadata/policies.js

@@ -273,7 +273,7 @@ var policies = {
         "message": "<p>Current latest versions of jQuery are 1.11 (with support for old IE versions) and 2.1 (without).</p><p>Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.</p>",
         "hasOffenders": false,
         "scoreFn": function(data) {
-            var differentVersions = data.toolsResults.phantomas.metrics.jQueryDifferentVersions;
+            var differentVersions = data.toolsResults.phantomas.metrics.jQueryVersionsLoaded;
 
             if (differentVersions === 0 || differentVersions > 1 || !data.toolsResults.phantomas.metrics.jQueryVersion) {
                 // Not applicable
@@ -322,7 +322,7 @@ var policies = {
             }
         }
     },
-    "jQueryDifferentVersions": {
+    "jQueryVersionsLoaded": {
         "tool": "phantomas",
         "label": "Several versions loaded",
         "message": "<p>jQuery is a heavy library. You should <b>never</b> load jQuery more than once on the same page.</p>",
@@ -410,6 +410,52 @@ var policies = {
             };
         }
     },
+    "cssColors": {
+        "tool": "phantomas",
+        "label": "Different colors",
+        "message": "<p>This is the number of different colors defined in CSS.</p><p>Your CSS project will be easier to maintain if you keep a small color set.</p>",
+        "isOkThreshold": 30,
+        "isBadThreshold": 150,
+        "isAbnormalThreshold": 300,
+        "hasOffenders": true,
+        "offendersTransformFn": function(offenders, ruleObject) {
+            var deduplicatedObj = {};
+
+            offenders.map(function(offender) {
+                var parts = /^([^ ]*) \((\d+) times\)$/.exec(offender);
+
+                if (!parts) {
+                    debug('cssColors offenders transform function error with "%s"', offender);
+                    return;
+                }
+
+                var color = parts[1];
+                var count = parseInt(parts[2], 10);
+
+                deduplicatedObj[color] = (deduplicatedObj[color] || 0) + count;
+            });
+
+            var deduplicatedTable = [];
+            for (var color in deduplicatedObj) {
+                deduplicatedTable.push({
+                    color: color,
+                    occurrences: deduplicatedObj[color]
+                });
+            }
+
+            deduplicatedTable.sort(function(a, b) {
+                return b.occurrences - a.occurrences;
+            });
+
+            // Override rules.value
+            ruleObject.value = deduplicatedTable.length;
+
+            return {
+                count: deduplicatedTable.length,
+                palette: deduplicatedTable
+            };
+        }
+    },
     "cssImports": {
         "tool": "phantomas",
         "label": "Uses of @import",

+ 3 - 2
lib/metadata/scoreProfileGeneric.json

@@ -32,7 +32,7 @@
             "label": "jQuery version",
             "policies": {
                 "jQueryVersion": 5,
-                "jQueryDifferentVersions": 0.1
+                "jQueryVersionsLoaded": 0.1
             }
         },
         "cssSyntaxError": {
@@ -46,7 +46,8 @@
             "policies": {
                 "cssRules": 2,
                 "cssComplexSelectors": 2,
-                "cssComplexSelectorsByAttribute": 1.5
+                "cssComplexSelectorsByAttribute": 1.5,
+                "cssColors": 0.5
             }
         },
         "badCSS": {

+ 1 - 1
lib/rulesChecker.js

@@ -65,7 +65,7 @@ var RulesChecker = function() {
                         if (policy.offendersTransformFn) {
 
                             try {
-                                offendersObj = policy.offendersTransformFn(offenders);
+                                offendersObj = policy.offendersTransformFn(offenders, rule);
                             } catch(err) {
                                 debug('Error while transforming offenders for %s', metricName);
                                 debug(err);

+ 62 - 48
lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js

@@ -7,16 +7,17 @@
 /* global document: true, window: true */
 /* jshint -W030 */
 
-exports.version = '0.2.a';
+exports.version = '1.0.a';
 
 exports.module = function(phantomas) {
     'use strict';
 
     phantomas.setMetric('jQueryVersion', ''); // @desc version of jQuery framework (if loaded) [string]
+    phantomas.setMetric('jQueryVersionsLoaded'); // @desc number of loaded jQuery "instances" (even in the same version)
     phantomas.setMetric('jQueryOnDOMReadyFunctions'); // @desc number of functions bound to onDOMReady event
+    phantomas.setMetric('jQueryWindowOnLoadFunctions'); // @desc number of functions bound to windowOnLoad event
     phantomas.setMetric('jQuerySizzleCalls'); // @desc number of calls to Sizzle (including those that will be resolved using querySelectorAll)
-    phantomas.setMetric('jQuerySizzleCallsDuplicated'); // @desc number of calls on the same Sizzle request
-    phantomas.setMetric('jQueryDifferentVersions'); //@desc number of different jQuery versions loaded on the page (not counting iframes)
+    //phantomas.setMetric('jQueryEventTriggers'); // @desc number of jQuery event triggers
 
     var jQueryFunctions = [
         // DOM manipulations
@@ -110,30 +111,28 @@ exports.module = function(phantomas) {
         phantomas.evaluate(function(jQueryFunctions) {
             (function(phantomas) {
                 var jQuery;
+                var oldJQuery;
 
-                // TODO: create a helper - phantomas.spyGlobalVar() ?
-                window.__defineSetter__('jQuery', function(val) {
+                phantomas.spyGlobalVar('jQuery', function(jQuery) {
                     var version;
-                    var jQueryFn;
-                    var oldJQuery = jQuery;
 
-                    if (!val || !val.fn) {
+                    if (!jQuery || !jQuery.fn) {
                         phantomas.log('jQuery: unable to detect version!');
                         return;
                     }
 
-                    version = val.fn.jquery;
-                    jQuery = val;
-                    jQueryFn = val.fn;
-                    // Older jQuery (v?.?) compatibility
-                    if (!jQueryFn) {
-                        jQueryFn = jQuery;
+                    // Tag the current version of jQuery to avoid multiple reports of jQuery being loaded
+                    // when it's actually only restored via $.noConflict(true) - see comments in #435
+                    if (jQuery.__phantomas === true) {
+                        phantomas.log('jQuery: this instance has already been seen by phantomas');
+                        return;
                     }
+                    jQuery.__phantomas = true;
 
-                    phantomas.log('jQuery: loaded v' + version);
-                    phantomas.setMetric('jQueryVersion', version);
+                    // report the version of jQuery
+                    version = jQuery.fn.jquery;
                     phantomas.emit('jQueryLoaded', version);
-                    
+
                     phantomas.pushContext({
                         type: (oldJQuery) ? 'jQuery version change' : 'jQuery loaded',
                         callDetails: {
@@ -141,11 +140,13 @@ exports.module = function(phantomas) {
                         },
                         backtrace: phantomas.getBacktrace()
                     });
+                    oldJQuery = version;
 
                     // jQuery.ready.promise
                     // works for jQuery 1.8.0+ (released Aug 09 2012)
-                    phantomas.spy(val.ready, 'promise', function(func) {
+                    phantomas.spy(jQuery.ready, 'promise', function(func) {
                         phantomas.incrMetric('jQueryOnDOMReadyFunctions');
+                        phantomas.addOffender('jQueryOnDOMReadyFunctions', phantomas.getCaller(3));
 
                         phantomas.pushContext({
                             type: 'jQuery - onDOMReady',
@@ -160,9 +161,9 @@ exports.module = function(phantomas) {
 
                     // Sizzle calls - jQuery.find
                     // works for jQuery 1.3+ (released Jan 13 2009)
-                    phantomas.spy(val, 'find', function(selector, context) {
+                    phantomas.spy(jQuery, 'find', function(selector, context) {
                         phantomas.incrMetric('jQuerySizzleCalls');
-                        phantomas.emit('onSizzleCall', selector + ' (context: ' + (phantomas.getDOMPath(context) || 'unknown') + ')');
+                        phantomas.addOffender('jQuerySizzleCalls', '%s (in %s)', selector, (phantomas.getDOMPath(context) || 'unknown'));
                         
                         phantomas.enterContext({
                             type: 'jQuery - find',
@@ -183,12 +184,39 @@ exports.module = function(phantomas) {
                         phantomas.leaveContext(moreData);
                     }) || phantomas.log('jQuery: can not measure jQuerySizzleCalls (jQuery used on the page is too old)!');
 
+                    /*if (!jQuery.event) {
+                        phantomas.spy(jQuery.event, 'trigger', function(ev, data, elem) {
+                            var path = phantomas.getDOMPath(elem),
+                                type = ev.type || ev;
+
+                            phantomas.log('Event: triggered "%s" on "%s"', type, path);
+
+                            phantomas.incrMetric('jQueryEventTriggers');
+                            phantomas.addOffender('jQueryEventTriggers', '"%s" on "%s"', type, path);
+                        });
+                    }*/
+
+                    // jQuery events bound to window' onLoad event (#451)
+                    phantomas.spy(jQuery.fn, 'on', function(eventName, func) {
+                        if ((eventName === 'load') && (this[0] === window)) {
+                            phantomas.incrMetric('jQueryWindowOnLoadFunctions');
+                            phantomas.addOffender('jQueryWindowOnLoadFunctions', phantomas.getCaller(2));
+
+                            phantomas.pushContext({
+                                type: 'jQuery - windowOnLoad',
+                                callDetails: {
+                                    arguments: [func]
+                                },
+                                backtrace: phantomas.getBacktrace()
+                            });
+                        }
+                    });
 
                     // Add spys on many jQuery functions
                     jQueryFunctions.forEach(function(functionName) {
                         var capitalizedName = functionName.substring(0,1).toUpperCase() + functionName.substring(1);
                         
-                        phantomas.spy(jQueryFn, functionName, function(args) {
+                        phantomas.spy(jQuery.fn, functionName, function(args) {
 
                             // Clean args
                             args = [].slice.call(arguments);
@@ -263,42 +291,28 @@ exports.module = function(phantomas) {
                             phantomas.leaveContext();
                         }) || phantomas.log('jQuery: can not track jQuery - ' + capitalizedName + ' (this version of jQuery doesn\'t support it)');
                     });
-
-
-                });
-
-                window.__defineGetter__('jQuery', function() {
-                    return jQuery;
                 });
             })(window.__phantomas);
         }, jQueryFunctions);
     });
 
 
-    // count Sizzle calls to detect duplicated queries
-    var Collection = require('../../util/collection'),
-        sizzleCalls = new Collection(),
-        jQueryLoading = new Collection();
-
-    phantomas.on('onSizzleCall', function(request) {
-        sizzleCalls.push(request);
+    // store the last resource that was received
+    // try to report where given jQuery version was loaded from
+    phantomas.on('recv', function(entry) {
+        if (entry.isJS) {
+            lastUrl = entry.url;
+        }
     });
 
     phantomas.on('jQueryLoaded', function(version) {
-        jQueryLoading.push(version);
-    });
+        phantomas.log('jQuery: loaded v' + version);
+        phantomas.setMetric('jQueryVersion', version);
+
+        // report multiple jQuery "instances" (issue #435)
+        phantomas.incrMetric('jQueryVersionsLoaded');
+        phantomas.addOffender('jQueryVersionsLoaded', 'v%s', version);
 
-    phantomas.on('report', function() {
-        sizzleCalls.sort().forEach(function(id, cnt) {
-            if (cnt > 1) {
-                phantomas.incrMetric('jQuerySizzleCallsDuplicated');
-                phantomas.addOffender('jQuerySizzleCallsDuplicated', '%s: %d', id, cnt);
-            }
-        });
-
-        jQueryLoading.forEach(function(version) {
-            phantomas.incrMetric('jQueryDifferentVersions');
-            phantomas.addOffender('jQueryDifferentVersions', '%s', version);
-        });
+        phantomas.log('jQuery: v%s (probably loaded from <%s>)', version, lastUrl);
     });
 };

+ 8 - 8
test/core/customPoliciesTest.js

@@ -404,20 +404,20 @@ describe('rulesChecker', function() {
         results.should.deep.equals({});
 
 
-        // If jQueryDifferentVersions is 0
+        // If jQueryVersionsLoaded is 0
         results = rulesChecker.check({
             "toolsResults": {
                 "phantomas": {
                     "metrics": {
                         "jQueryVersion": "1.6.0",
-                        "jQueryDifferentVersions": 0
+                        "jQueryVersionsLoaded": 0
                     }
                 }
             }
         }, policies);
         results.should.not.have.a.property('jQueryVersion');
-        results.should.have.a.property('jQueryDifferentVersions');
-        results.jQueryDifferentVersions.should.have.a.property('score').that.equals(100);
+        results.should.have.a.property('jQueryVersionsLoaded');
+        results.jQueryVersionsLoaded.should.have.a.property('score').that.equals(100);
 
 
         // If there are more than 1 jQuery version
@@ -426,15 +426,15 @@ describe('rulesChecker', function() {
                 "phantomas": {
                     "metrics": {
                         "jQueryVersion": "1.6.0",
-                        "jQueryDifferentVersions": 2
+                        "jQueryVersionsLoaded": 2
                     }
                 }
             }
         }, policies);
         results.should.not.have.a.property('jQueryVersion');
-        results.should.have.a.property('jQueryDifferentVersions');
-        results.jQueryDifferentVersions.should.have.a.property('score').that.equals(0);
-        results.jQueryDifferentVersions.should.have.a.property('abnormal').that.equals(true);
+        results.should.have.a.property('jQueryVersionsLoaded');
+        results.jQueryVersionsLoaded.should.have.a.property('score').that.equals(0);
+        results.jQueryVersionsLoaded.should.have.a.property('abnormal').that.equals(true);
     });