Просмотр исходного кода

Add new CSS metrics with analyze-css

Gaël Métais 10 лет назад
Родитель
Сommit
ab100010d8
3 измененных файлов с 225 добавлено и 108 удалено
  1. 1 0
      app/lib/phantomasWrapper.js
  2. 26 0
      app/node_views/results.html
  3. 198 108
      app/public/scripts/resultsCtrl.js

+ 1 - 0
app/lib/phantomasWrapper.js

@@ -25,6 +25,7 @@ var PhantomasWrapper = function() {
 
 
             // Mandatory
             // Mandatory
             reporter: 'json:pretty',
             reporter: 'json:pretty',
+            'analyze-css': true,
             'skip-modules': [
             'skip-modules': [
                 //'ajaxRequests',
                 //'ajaxRequests',
                 //'alerts',
                 //'alerts',

+ 26 - 0
app/node_views/results.html

@@ -112,6 +112,32 @@
                         </ul>
                         </ul>
                     </div>
                     </div>
                 </div>
                 </div>
+                <div>
+                    <div ng-class="notations.cssComplexity">{{notations.cssComplexity}}</div>
+                    <div class="notation">CSS complexity</div>
+                    <div class="criteria">
+                        <ul>
+                            <li ng-if="phantomasResults.metrics.cssRules">Rules count: {{phantomasResults.metrics.cssRules}}</li>
+                            <li ng-if="phantomasResults.metrics.cssComplexSelectors">Complex selectors: {{phantomasResults.metrics.cssComplexSelectors}}</li>
+                        </ul>
+                    </div>
+                </div>
+                <div>
+                    <div ng-class="notations.badCss">{{notations.badCss}}</div>
+                    <div class="notation">Bad CSS</div>
+                    <div class="criteria">
+                        <ul>
+                            <li ng-if="phantomasResults.metrics.cssDuplicatedSelectors">Duplicated selectors: {{phantomasResults.metrics.cssDuplicatedSelectors}}</li>
+                            <li ng-if="phantomasResults.metrics.cssEmptyRules">Empty rules: {{phantomasResults.metrics.cssEmptyRules}}</li>
+                            <li ng-if="phantomasResults.metrics.cssExpressions">CSS expressions: {{phantomasResults.metrics.cssExpressions}}</li>
+                            <li ng-if="phantomasResults.metrics.cssImportants">Uses of !important: {{phantomasResults.metrics.cssImportants}}</li>
+                            <li ng-if="phantomasResults.metrics.cssOldIEFixes">Old IE fixes: {{phantomasResults.metrics.cssOldIEFixes}}</li>
+                            <li ng-if="phantomasResults.metrics.cssOldPropertyPrefixes">Old prefixes: {{phantomasResults.metrics.cssOldPropertyPrefixes}}</li>
+                            <li ng-if="phantomasResults.metrics.cssUniversalSelectors">Universal * selectors: {{phantomasResults.metrics.cssUniversalSelectors}}</li>
+                            <li ng-if="phantomasResults.metrics.cssRedundantBodySelectors">Redundant body selectors: {{phantomasResults.metrics.cssRedundantBodySelectors}}</li>
+                        </ul>
+                    </div>
+                </div>
             </div>
             </div>
         </div>
         </div>
 
 

+ 198 - 108
app/public/scripts/resultsCtrl.js

@@ -67,127 +67,227 @@ app.controller('ResultsCtrl', function ($scope) {
         }
         }
 
 
         $scope.notations = {
         $scope.notations = {
-            domComplexity: 'A',
-            domManipulations: 'A',
-            duplicatedDomQueries: 'A',
-            eventsBound: 'A',
-            badPractices: 'A',
-            scripts: 'A',
-            jQueryLoading: 'A'
+            domComplexity: getDomComplexityScore(),
+            domManipulations: getDomManipulationsScore(),
+            duplicatedDomQueries: getDuplicatedDomQueriesScore(),
+            eventsBound: getEventsBoundScore(),
+            badPractices: getBadPracticesScore(),
+            scripts: getScriptsScore(),
+            jQueryLoading: getJQueryLoadingScore(),
+            cssComplexity: getCSSComplexityScore(),
+            badCss: getBadCssScore()
         };
         };
+    }
+
+    function initExecutionView() {
+        $scope.slowRequestsOn = false;
+        $scope.slowRequestsLimit = 5;
+
+        if (!$scope.javascript.children) {
+            return;
+        }
+
+
+        // Now read the tree and display it on a timeline
+        
+        // Split the timeline into 200 intervals
+        var numberOfIntervals = 200;
+        var lastEvent = $scope.javascript.children[$scope.javascript.children.length - 1];
+        $scope.endTime =  lastEvent.data.timestamp + (lastEvent.data.time || 0);
+        $scope.timelineIntervalDuration = $scope.endTime / numberOfIntervals;
+        
+        // Pre-filled array of 100 elements
+        $scope.timeline = Array.apply(null, new Array(numberOfIntervals)).map(Number.prototype.valueOf,0);
+
+        treeRunner($scope.javascript, function(node) {
+            
+            if (node.data.time) {
+                
+                // If a node is between two intervals, split it. That's the meaning of the following dirty algorithm.
+
+                var startInterval = Math.floor(node.data.timestamp / $scope.timelineIntervalDuration);
+                var endInterval = Math.floor((node.data.timestamp + node.data.time) / $scope.timelineIntervalDuration);
+
+                if (startInterval === endInterval) {
+                    
+                    $scope.timeline[startInterval] += node.data.time;
+
+                } else {
+                    
+                    var timeToDispatch = node.data.time;
+                    
+                    var startIntervalPart = ((startInterval + 1) * $scope.timelineIntervalDuration) - node.data.timestamp;
+                    $scope.timeline[startInterval] += startIntervalPart;
+                    timeToDispatch -= startIntervalPart;
+                    
+                    var currentInterval = startInterval;
+                    while(currentInterval < endInterval && currentInterval + 1 < numberOfIntervals) {
+                        currentInterval ++;
+                        var currentIntervalPart = Math.min(timeToDispatch, $scope.timelineIntervalDuration);
+                        $scope.timeline[currentInterval] = currentIntervalPart;
+                        timeToDispatch -= currentIntervalPart;
+                    }
+                }
+            }
+            
+            if (node.data.type !== 'main') {
+                // Don't check the children
+                return false;
+            }
+        });
+        $scope.timelineMax = Math.max.apply(Math, $scope.timeline);
+    }
 
 
+    function initMetricsView() {
+        // Get the Phantomas modules from metadata
+        $scope.metricsModule = {};
+        for (var metricName in $scope.phantomasMetadata) {
+            var metric = $scope.phantomasMetadata[metricName];
+            if (!$scope.metricsModule[metric.module]) {
+                $scope.metricsModule[metric.module] = {};
+            }
+            $scope.metricsModule[metric.module][metricName] = metric;
+        }
+    }
+
+
+    function getDomComplexityScore() {
+        var note = 'A';
         var domComplexityScore = $scope.phantomasResults.metrics.DOMelementsCount +
         var domComplexityScore = $scope.phantomasResults.metrics.DOMelementsCount +
                                  Math.pow($scope.phantomasResults.metrics.DOMelementMaxDepth, 2) +
                                  Math.pow($scope.phantomasResults.metrics.DOMelementMaxDepth, 2) +
                                  $scope.phantomasResults.metrics.iframesCount * 50;
                                  $scope.phantomasResults.metrics.iframesCount * 50;
         if (domComplexityScore > 1000) {
         if (domComplexityScore > 1000) {
-            $scope.notations.domComplexity = 'B';
+            note = 'B';
         }
         }
         if (domComplexityScore > 1500) {
         if (domComplexityScore > 1500) {
-            $scope.notations.domComplexity = 'C';
+            note = 'C';
         }
         }
         if (domComplexityScore > 2000) {
         if (domComplexityScore > 2000) {
-            $scope.notations.domComplexity = 'D';
+            note = 'D';
         }
         }
         if (domComplexityScore > 3000) {
         if (domComplexityScore > 3000) {
-            $scope.notations.domComplexity = 'E';
+            note = 'E';
         }
         }
         if (domComplexityScore > 4000) {
         if (domComplexityScore > 4000) {
-            $scope.notations.domComplexity = 'F';
+            note = 'F';
         }
         }
+        return note;
+    }
 
 
+    function getDomManipulationsScore() {
+        var note = 'A';
         var domManipulationsScore = $scope.phantomasResults.metrics.DOMinserts +
         var domManipulationsScore = $scope.phantomasResults.metrics.DOMinserts +
                                     $scope.phantomasResults.metrics.DOMqueries * 0.5 +
                                     $scope.phantomasResults.metrics.DOMqueries * 0.5 +
                                     $scope.totalJSTime;
                                     $scope.totalJSTime;
         if (domManipulationsScore > 100) {
         if (domManipulationsScore > 100) {
-            $scope.notations.domManipulations = 'B';
+            note = 'B';
         }
         }
         if (domManipulationsScore > 200) {
         if (domManipulationsScore > 200) {
-            $scope.notations.domManipulations = 'C';
+            note = 'C';
         }
         }
         if (domManipulationsScore > 300) {
         if (domManipulationsScore > 300) {
-            $scope.notations.domManipulations = 'D';
+            note = 'D';
         }
         }
         if (domManipulationsScore > 500) {
         if (domManipulationsScore > 500) {
-            $scope.notations.domManipulations = 'E';
+            note = 'E';
         }
         }
         if (domManipulationsScore > 800) {
         if (domManipulationsScore > 800) {
-            $scope.notations.domManipulations = 'F';
+            note = 'F';
         }
         }
+        return note;
+    }
 
 
+    function getDuplicatedDomQueriesScore() {
+        var note = 'A';
         var duplicatedDomQueries = $scope.duplicatedQueriesCountAll;
         var duplicatedDomQueries = $scope.duplicatedQueriesCountAll;
         if (duplicatedDomQueries > 10) {
         if (duplicatedDomQueries > 10) {
-            $scope.notations.duplicatedDomQueries = 'B';
+            note = 'B';
         }
         }
         if (duplicatedDomQueries > 50) {
         if (duplicatedDomQueries > 50) {
-            $scope.notations.duplicatedDomQueries = 'C';
+            note = 'C';
         }
         }
         if (duplicatedDomQueries > 100) {
         if (duplicatedDomQueries > 100) {
-            $scope.notations.duplicatedDomQueries = 'D';
+            note = 'D';
         }
         }
         if (duplicatedDomQueries > 200) {
         if (duplicatedDomQueries > 200) {
-            $scope.notations.duplicatedDomQueries = 'E';
+            note = 'E';
         }
         }
         if (duplicatedDomQueries > 500) {
         if (duplicatedDomQueries > 500) {
-            $scope.notations.duplicatedDomQueries = 'F';
+            note = 'F';
         }
         }
+        return note;
+    }
 
 
+    function getEventsBoundScore() {
+        var note = 'A';
         var eventsBoundScore = $scope.phantomasResults.metrics.eventsBound;
         var eventsBoundScore = $scope.phantomasResults.metrics.eventsBound;
         if (eventsBoundScore > 50) {
         if (eventsBoundScore > 50) {
-            $scope.notations.eventsBound = 'B';
+            note = 'B';
         }
         }
         if (eventsBoundScore > 100) {
         if (eventsBoundScore > 100) {
-            $scope.notations.eventsBound = 'C';
+            note = 'C';
         }
         }
         if (eventsBoundScore > 200) {
         if (eventsBoundScore > 200) {
-            $scope.notations.eventsBound = 'D';
+            note = 'D';
         }
         }
         if (eventsBoundScore > 500) {
         if (eventsBoundScore > 500) {
-            $scope.notations.eventsBound = 'E';
+            note = 'E';
         }
         }
         if (eventsBoundScore > 1000) {
         if (eventsBoundScore > 1000) {
-            $scope.notations.eventsBound = 'F';
+            note = 'F';
         }
         }
+        return note;
+    }
 
 
+    function getBadPracticesScore() {
+        var note = 'A';
         var badPracticesScore = $scope.phantomasResults.metrics.documentWriteCalls * 3 +
         var badPracticesScore = $scope.phantomasResults.metrics.documentWriteCalls * 3 +
                                 $scope.phantomasResults.metrics.evalCalls * 3 +
                                 $scope.phantomasResults.metrics.evalCalls * 3 +
                                 $scope.phantomasResults.metrics.jsErrors * 10 +
                                 $scope.phantomasResults.metrics.jsErrors * 10 +
                                 $scope.phantomasResults.metrics.consoleMessages;
                                 $scope.phantomasResults.metrics.consoleMessages;
         if (badPracticesScore > 5) {
         if (badPracticesScore > 5) {
-            $scope.notations.badPractices = 'B';
+            note = 'B';
         }
         }
         if (badPracticesScore > 10) {
         if (badPracticesScore > 10) {
-            $scope.notations.badPractices = 'C';
+            note = 'C';
         }
         }
         if (badPracticesScore > 15) {
         if (badPracticesScore > 15) {
-            $scope.notations.badPractices = 'D';
+            note = 'D';
         }
         }
         if (badPracticesScore > 25) {
         if (badPracticesScore > 25) {
-            $scope.notations.badPractices = 'E';
+            note = 'E';
         }
         }
         if (badPracticesScore > 40) {
         if (badPracticesScore > 40) {
-            $scope.notations.badPractices = 'F';
+            note = 'F';
         }
         }
+        return note;
+    }
 
 
+    function getScriptsScore() {
+        var note = 'A';
         var scriptsScore = $scope.phantomasResults.metrics.jsCount;
         var scriptsScore = $scope.phantomasResults.metrics.jsCount;
         if (scriptsScore > 4) {
         if (scriptsScore > 4) {
-            $scope.notations.scripts = 'B';
+            note = 'B';
         }
         }
         if (scriptsScore > 8) {
         if (scriptsScore > 8) {
-            $scope.notations.scripts = 'C';
+            note = 'C';
         }
         }
         if (scriptsScore > 12) {
         if (scriptsScore > 12) {
-            $scope.notations.scripts = 'D';
+            note = 'D';
         }
         }
         if (scriptsScore > 16) {
         if (scriptsScore > 16) {
-            $scope.notations.scripts = 'E';
+            note = 'E';
         }
         }
         if (scriptsScore > 20) {
         if (scriptsScore > 20) {
-            $scope.notations.scripts = 'F';
+            note = 'F';
         }
         }
+        return note;
+    }
 
 
-        $scope.notations.jQueryLoading = 'NA';
+    function getJQueryLoadingScore() {
+        var note = 'NA';
         if ($scope.phantomasResults.metrics.jQueryDifferentVersions > 1) {
         if ($scope.phantomasResults.metrics.jQueryDifferentVersions > 1) {
-            $scope.notations.jQueryLoading = 'F';
+            note = 'F';
         } else if ($scope.phantomasResults.metrics.jQueryVersion) {
         } else if ($scope.phantomasResults.metrics.jQueryVersion) {
             if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.10.') === 0 ||
             if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.10.') === 0 ||
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.11.') === 0 ||
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.11.') === 0 ||
@@ -195,94 +295,84 @@ app.controller('ResultsCtrl', function ($scope) {
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.0.') === 0 ||
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.0.') === 0 ||
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.1.') === 0 ||
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.1.') === 0 ||
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.2.') === 0) {
                 $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.2.') === 0) {
-                $scope.notations.jQueryLoading = 'A';
+                note = 'A';
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.8.') === 0 ||
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.8.') === 0 ||
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.9.') === 0) {
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.9.') === 0) {
-                $scope.notations.jQueryLoading = 'B';
+                note = 'B';
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.6.') === 0 ||
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.6.') === 0 ||
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.7.') === 0) {
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.7.') === 0) {
-                $scope.notations.jQueryLoading = 'C';
+                note = 'C';
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.4.') === 0 ||
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.4.') === 0 ||
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.5.') === 0) {
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.5.') === 0) {
-                $scope.notations.jQueryLoading = 'D';
+                note = 'D';
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.2.') === 0 ||
             } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.2.') === 0 ||
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.3.') === 0) {
                        $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.3.') === 0) {
-                $scope.notations.jQueryLoading = 'E';
+                note = 'E';
             }
             }
         }
         }
+        return note;
     }
     }
 
 
-    function initExecutionView() {
-        $scope.slowRequestsOn = false;
-        $scope.slowRequestsLimit = 5;
-
-        if (!$scope.javascript.children) {
-            return;
+    function getCSSComplexityScore() {
+        if (!$scope.phantomasResults.metrics.cssRules) {
+            return 'NA';
         }
         }
 
 
-
-        // Now read the tree and display it on a timeline
-        
-        // Split the timeline into 200 intervals
-        var numberOfIntervals = 200;
-        var lastEvent = $scope.javascript.children[$scope.javascript.children.length - 1];
-        $scope.endTime =  lastEvent.data.timestamp + (lastEvent.data.time || 0);
-        $scope.timelineIntervalDuration = $scope.endTime / numberOfIntervals;
-        
-        // Pre-filled array of 100 elements
-        $scope.timeline = Array.apply(null, new Array(numberOfIntervals)).map(Number.prototype.valueOf,0);
-
-        treeRunner($scope.javascript, function(node) {
-            
-            if (node.data.time) {
-                
-                // If a node is between two intervals, split it. That's the meaning of the following dirty algorithm.
-
-                var startInterval = Math.floor(node.data.timestamp / $scope.timelineIntervalDuration);
-                var endInterval = Math.floor((node.data.timestamp + node.data.time) / $scope.timelineIntervalDuration);
-
-                if (startInterval === endInterval) {
-                    
-                    $scope.timeline[startInterval] += node.data.time;
-
-                } else {
-                    
-                    var timeToDispatch = node.data.time;
-                    
-                    var startIntervalPart = ((startInterval + 1) * $scope.timelineIntervalDuration) - node.data.timestamp;
-                    $scope.timeline[startInterval] += startIntervalPart;
-                    timeToDispatch -= startIntervalPart;
-                    
-                    var currentInterval = startInterval;
-                    while(currentInterval < endInterval && currentInterval + 1 < numberOfIntervals) {
-                        currentInterval ++;
-                        var currentIntervalPart = Math.min(timeToDispatch, $scope.timelineIntervalDuration);
-                        $scope.timeline[currentInterval] = currentIntervalPart;
-                        timeToDispatch -= currentIntervalPart;
-                    }
-                }
-            }
-            
-            if (node.data.type !== 'main') {
-                // Don't check the children
-                return false;
-            }
-        });
-        $scope.timelineMax = Math.max.apply(Math, $scope.timeline);
+        var note = 'A';
+        var cssScore = $scope.phantomasResults.metrics.cssRules +
+                       $scope.phantomasResults.metrics.cssComplexSelectors * 10;
+        if (cssScore > 200) {
+            note = 'B';
+        }
+        if (cssScore > 500) {
+            note = 'C';
+        }
+        if (cssScore > 1000) {
+            note = 'D';
+        }
+        if (cssScore > 2000) {
+            note = 'E';
+        }
+        if (cssScore > 5000) {
+            note = 'F';
+        }
+        return note;
     }
     }
 
 
-    function initMetricsView() {
-        // Get the Phantomas modules from metadata
-        $scope.metricsModule = {};
-        for (var metricName in $scope.phantomasMetadata) {
-            var metric = $scope.phantomasMetadata[metricName];
-            if (!$scope.metricsModule[metric.module]) {
-                $scope.metricsModule[metric.module] = {};
-            }
-            $scope.metricsModule[metric.module][metricName] = metric;
+    function getBadCssScore() {
+        if (!$scope.phantomasResults.metrics.cssRules) {
+            return 'NA';
+        }
+
+        var note = 'A';
+        var badCssScore = $scope.phantomasResults.metrics.cssDuplicatedSelectors +
+                          $scope.phantomasResults.metrics.cssEmptyRules +
+                          $scope.phantomasResults.metrics.cssExpressions * 10 +
+                          $scope.phantomasResults.metrics.cssImportants * 2 +
+                          $scope.phantomasResults.metrics.cssOldIEFixes * 10 +
+                          $scope.phantomasResults.metrics.cssOldPropertyPrefixes +
+                          $scope.phantomasResults.metrics.cssUniversalSelectors * 5
+                          $scope.phantomasResults.metrics.cssRedundantBodySelectors
+        if (badCssScore > 20) {
+            note = 'B';
+        }
+        if (badCssScore > 50) {
+            note = 'C';
+        }
+        if (badCssScore > 100) {
+            note = 'D';
         }
         }
+        if (badCssScore > 200) {
+            note = 'E';
+        }
+        if (badCssScore > 500) {
+            note = 'F';
+        }
+        return note;
     }
     }
 
 
+
+
     function parseBacktrace(str) {
     function parseBacktrace(str) {
         if (!str) {
         if (!str) {
             return null;
             return null;