UNPKG

plato

Version:

JavaScript source analysis and visualizer

288 lines (241 loc) 8.39 kB
/* * plato * https://github.com/es-analysis/plato * * Copyright (c) 2012 Jarrod Overson * Licensed under the MIT license. */ 'use strict'; // node api var path = require('path'); // node api with sugar var fs = require('fs-extra'); // vendor var _ = require('lodash'); var glob = require('glob'); // local lib var util = require('./util'), OverviewHistory = require('./models/OverviewHistory'), FileHistory = require('./models/FileHistory'), Logger = require('./logger'), reporters = { complexity : require('./reporters/complexity'), jshint : require('./reporters/jshint'), eslint : require('./reporters/eslint') }; var overviewTemplate = __dirname + '/templates/overview.html', displayTemplate = __dirname + '/templates/display.html', fileTemplate = __dirname + '/templates/file.html', assets = __dirname + '/assets/', fileDir = 'files'; var log = new Logger(Logger.WARNING); function unary(fn) { return function(a){ return fn(a);}; } exports.inspect = function(files, outputDir, options, done) { if (!files) { // at least give me a file man... return; } files = files instanceof Array ? files : [files]; files = _.flatten(files.map(unary(glob.sync))); var flags = { complexity : { commonjs : true, logicalor : true, switchcase : true, forin : false, trycatch : false, newmi : true }, jshint : {} // use jshint defaults }; if (options.eslint) { options.jshint = options.eslint; reporters.jshint = reporters.eslint; delete options.eslint; delete reporters.eslint; } Object.keys(flags).forEach(function(flag){ if (flag in options) flags[flag] = _.clone(options[flag]); }); if (options.q) log.level = Logger.ERROR; if (options.date) { // if we think we were given seconds if (options.date < 10000000000 ) options.date = options.date * 1000; options.date = new Date(options.date); } var reports = []; var fileOutputDir = outputDir ? path.join(outputDir, fileDir) : false; var commonBasePath = util.findCommonBase(files); var runReports = function(files, done) { files.forEach(function(file) { if (options.exclude && options.exclude.test(file)) return; if (options.recurse && fs.statSync(file).isDirectory()) { files = fs.readdirSync(file).map(function(innerFile) { return path.join(file,innerFile); }); runReports(files); } else if (file.match(/\.js$/)) { log.info('Reading "%s"', file); var fileShort = file.replace(commonBasePath, ''); var fileSafe = fileShort.replace(/[^a-zA-Z0-9]/g,'_'); var source = fs.readFileSync(file).toString().trim(); if (!source) { log.info('Not parsing empty file "%s"', file); return; } // if skip empty line option if(options.noempty) { source = source.replace(/^\s*[\r\n]/gm ,""); } // if begins with shebang if (source[0] === '#' && source[1] === '!') { source = '//' + source; } var report = { info : { file : file, fileShort : fileShort, fileSafe : fileSafe, link : fileDir + '/' + fileSafe + '/index.html' } }; var error = false; _.each(reporters,function(reporter, name) { if (!flags[name]) return; try { report[name] = reporter.process(source, flags[name], report.info); } catch (e) { error = true; log.error('Error reading file %s: ', file, e.toString()); log.error(e.stack); } }); if (error) return; reports.push(report); if (fileOutputDir) { var outdir = path.join(fileOutputDir, report.info.fileSafe); if (!fs.existsSync(outdir)) fs.mkdirSync(outdir); writeFileReport(outdir, report, source, options); } } }); if (done) done(); }; if (!fileOutputDir) { runReports(files,function() { done(reports); }); } else { fs.mkdirp(fileOutputDir,function() { runReports(files,function() { var reportFilePrefix = path.join(outputDir, 'report'); var overview = path.join(outputDir, 'index.html'); var wallDisplay = path.join(outputDir, 'display.html'); fs.copy(assets, path.join(outputDir, 'assets'), function() { var overviewReport = exports.getOverviewReport(reports); updateHistoricalOverview(reportFilePrefix, overviewReport, options); writeReport(reportFilePrefix, overviewReport); writeOverview(overview, overviewReport, { title : options.title, flags : flags }, overviewTemplate); writeOverview(wallDisplay, overviewReport, { title : options.title, flags : flags }, displayTemplate); done(reports); }); }); }); } }; // Filters out information unused in the overview for space/performance exports.getOverviewReport = function (reports) { var culledReports = []; var summary = { total : { jshint: 0, sloc : 0, maintainability : 0 }, average : { sloc : 0, maintainability : 0 } }; reports.forEach(function(report) { // clone objects so we don't have to worry about side effects summary.total.sloc += report.complexity.methodAggregate.sloc.physical; summary.total.maintainability += report.complexity.maintainability; var methodAggregate = _.cloneDeep(report.complexity.methodAggregate); var reportItem = {}; reportItem.info = report.info; if (report.jshint) { summary.total.jshint += report.jshint.messages.length; reportItem.jshint = { messages : report.jshint.messages.length }; } if (report.complexity) { reportItem.complexity = { methodAggregate : methodAggregate, module : report.complexity.module, module_safe : report.complexity.module_safe, maintainability : _.cloneDeep(report.complexity.maintainability) }; } culledReports.push(reportItem); }); summary.average.sloc = ~~(summary.total.sloc / reports.length); summary.average.jshint = (summary.total.jshint / reports.length).toFixed(2); summary.average.maintainability = (summary.total.maintainability / reports.length).toFixed(2); return { summary : summary, reports : culledReports }; }; function updateHistoricalOverview(outfilePrefix, overview, options) { var existingData = util.readJSON(outfilePrefix + '.history.json', options) || {}; var history = new OverviewHistory(existingData); history.addReport(overview, options.date); writeReport(outfilePrefix + '.history', history.toJSON(), '__history'); } function updateHistoricalReport(outfilePrefix, overview, options) { var existingData = util.readJSON(outfilePrefix + '.history.json', options) || {}; var history = new FileHistory(existingData); overview.date = options.date; history.addReport(overview, options.date); writeReport(outfilePrefix + '.history', history.toJSON(), '__history'); } function writeFile(file, source) { log.info('Writing file "%s".', file); fs.writeFileSync(file, source, 'utf8'); } function writeReport(outfilePrefix, report, exportName) { var formatted = util.formatJSON(report); writeFile(outfilePrefix + '.json', formatted); exportName = exportName || '__report'; var module = exportName + ' = ' + formatted; writeFile(outfilePrefix + '.js', module); } function writeOverview(outfile, report, options, templatePath) { var overviewSource = fs.readFileSync(templatePath).toString(); var template = _.template(overviewSource); var parsed = template({ report : report, options : options }); writeFile(outfile, parsed); } function writeFileReport(outdir, report, source, options) { var overviewSource = fs.readFileSync(fileTemplate).toString(); var parsed = _.template(overviewSource)({ source : util.escapeHTML(source), report : report }); var indexPath = path.join(outdir,'index.html'); var outfilePrefix = path.join(outdir,'report'); writeFile(indexPath, parsed); updateHistoricalReport(outfilePrefix, report, options); writeReport(outfilePrefix, report); }