Created
December 28, 2017 17:24
-
-
Save jsteinshouer/9e3556e5940f86388f9ecd91d129b78d to your computer and use it in GitHub Desktop.
Example Running CFLint with CommandBox Task Runner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* CommandBox Task for running cflint | |
* | |
* build/tasks/CFLint.cfc | |
*/ | |
component output="false" accessors="true" { | |
/* | |
* Constructor | |
*/ | |
function init() { | |
variables.rootPath = fileSystemUtil.resolvePath( '.' ); | |
variables.cflintJAR = rootPath & "bin/cflint-1.2.3-all/cflint-1.2.3-all.jar"; | |
variables.reportTemplate = "report.cfm"; | |
variables.htmlResultFile = rootPath & "cflint-results.html"; | |
} | |
/* | |
* Default target | |
*/ | |
public function run( pattern = "**.cfc", html = false) { | |
checkIfInstalled(); | |
var fullFilePaths = getInstance( 'globber' ) | |
.setPattern( rootPath & arguments.pattern ) | |
.matches(); | |
// Remove path from files to shorten the command string. Limit is 8191 characters on windows | |
var files = fullFilePaths.map( function(item) { | |
return replace(item, rootPath, ""); | |
}); | |
/* Run the report */ | |
runReport( files, arguments.html ); | |
} | |
private void function runReport( required files , html = false ) { | |
var reportData = getReportData( files ); | |
if ( html ) { | |
htmlReport( reportData ); | |
print.greenLine("Report generated at #htmlResultFile#"); | |
/* Open the browser. Windows specific */ | |
print.greenLine("Opening browser..."); | |
var runtime = createObject( "java", "java.lang.Runtime" ).getRuntime(); | |
runtime.exec( [ "rundll32", "url.dll,FileProtocolHandler", "file:///#rootPath#/cflint-results.html" ] ); | |
} | |
else { | |
displayReport( reportData ); | |
} | |
/* Make the task fail if an error exists */ | |
if ( reportData.errorExists ) { | |
/* Flush any output to the console */ | |
print.line().toConsole(); | |
error("Please fix errors found by CFLint!"); | |
} | |
} | |
/* | |
* Get results from cflint and create a data structure we can use to display results | |
*/ | |
private struct function getReportData( required array files ) { | |
var data = { | |
"version" = "1.2.3", | |
"timestamp" = now(), | |
"files" = {}, | |
"errorExists" = false | |
}; | |
var cflintResults = runCFLint( arguments.files ); | |
data.counts = cflintResults.counts; | |
for (var issue in cflintResults.issues) { | |
for ( var item in issue.locations ) { | |
if ( !structKeyExists( data.files, item.file ) ) { | |
data.files[ item.file ] = []; | |
} | |
var newIssue = { | |
severity = issue.severity, | |
id = issue.id, | |
message = item.message, | |
line = item.line, | |
column = item.column, | |
expression = item.expression | |
}; | |
switch ( issue.severity ) { | |
case "ERROR": | |
newIssue.color = "red"; | |
data.errorExists = true; | |
break; | |
case "WARNING": | |
newIssue.color = "yellow"; | |
break; | |
default: | |
newIssue.color = "magenta"; | |
} | |
data.files[ item.file ].append( newIssue ); | |
} | |
} | |
return data; | |
} | |
/* | |
* Run cflint on files and get result data structure | |
*/ | |
private struct function runCFLint( required array files ) { | |
var outputFile = "#rootPath#/cflint-out.json"; | |
command("!java -jar ""#cflintJAR#"" -logerror -quiet -file ""#files.toList()#"" -json -jsonfile ""#outputFile#""") | |
.inWorkingDirectory( rootPath ) | |
.run( returnOutput=true ); | |
var output = fileRead( outputFile ); | |
fileDelete( outputFile ); | |
return deserializeJSON( output ); | |
} | |
/* | |
* Generate an html report | |
*/ | |
private string function htmlReport( required data ) { | |
var content = ""; | |
savecontent variable="content" { | |
include reportTemplate; | |
} | |
if ( fileExists( htmlResultFile ) ) { | |
fileDelete( htmlResultFile ); | |
} | |
fileWrite( htmlResultFile, content ); | |
} | |
/* | |
* Display the report in the console | |
*/ | |
private void function displayReport( required data ) { | |
displaySummary( data ); | |
print.line(); | |
for ( var file in data.files ) { | |
print.greenLine( chr(9) & file & " " & data.files[ file ].len() ); | |
for (var issue in data.files[ file ]) { | |
print.text( repeatString( chr(9),2 ) ); | |
print.text(issue.severity, issue.color); | |
print.text( ": "); | |
print.boldText(issue.id); | |
print.text(", #issue.message# "); | |
print.cyanLine("[#issue.line#,#issue.column#]"); | |
} | |
} | |
} | |
/* | |
* Displays summary of results in the console | |
*/ | |
private void function displaySummary( required data ) { | |
print.line(); | |
print.greenLine( chr(9) & "Total Files:" & chr(9) & data.counts.totalFiles ); | |
print.greenLine( chr(9) & "Total Lines:" & chr(9) & data.counts.totalLines ); | |
for (var item in data.counts.countBySeverity ) { | |
print.text(chr(9)); | |
switch (item.severity) { | |
case "ERROR": | |
print.boldRedText("ERRORS:" & chr(9) & chr(9)); | |
break; | |
case "WARNING": | |
print.boldYellowText( "WARNINGS:" & chr(9) ); | |
break; | |
default: | |
print.boldMagentaText( item.severity & ":" & chr(9) & chr(9) ); | |
} | |
print.line( item.count ); | |
} | |
} | |
/* | |
* Install CFLint if not already installed | |
*/ | |
private void function checkIfInstalled() { | |
if ( !fileExists(cflintJAR) ) { | |
command("install") | |
.inWorkingDirectory( rootPath ) | |
.params( | |
id = "jar:https://github.com/cflint/CFLint/releases/download/CFLint-1.2.3/CFLint-1.2.3-all.jar", | |
directory = "./bin" | |
) | |
.run(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
Template to generate an html report for cflint results | |
build/resources/cflint/report.cfm | |
---> | |
<!doctype html> | |
<html lang="en"> | |
<head> | |
<title>CFLint Results</title> | |
<!-- Required meta tags --> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | |
<!-- Bootstrap CSS --> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/open-iconic/1.1.1/font/css/open-iconic.min.css" crossorigin="anonymous"> | |
</head> | |
<body> | |
<div class="container" style="margin-top: 30px; margin-bottom: 40px"> | |
<h2>CFLint Results</h2> | |
<cfoutput> | |
<table class="table table-bordered table-sm"> | |
<tbody> | |
<tr> | |
<th>Version</th> | |
<td>#data.version#</td> | |
</tr> | |
<tr> | |
<th>Timestamp</th> | |
<td>#dateTimeFormat(data.timestamp)#</td> | |
</tr> | |
<tr> | |
<th>Files</th> | |
<td>#data.counts.totalFiles#</td> | |
</tr> | |
<tr> | |
<th>Lines</th> | |
<td>#data.counts.totalLines#</td> | |
</tr> | |
<cfloop array="#data.counts.countBySeverity#" index="item"> | |
<tr> | |
<th> | |
<cfswitch expression="#item.severity#"> | |
<cfcase value="ERROR"><span class="oi" data-glyph="bug"></span></cfcase> | |
<cfcase value="WARNING"><span class="oi" data-glyph="warning"></span></cfcase> | |
<cfdefaultcase><span class="oi" data-glyph="info"></span></cfdefaultcase> | |
</cfswitch> | |
#item.severity# | |
</th> | |
<td>#item.count#</td> | |
</tr> | |
</cfloop> | |
</tbody> | |
</table> | |
<div id="accordion" role="tablist"> | |
<cfset index = 1> | |
<cfloop collection="#data.files#" key="file"> | |
<div class="card"> | |
<div class="card-header" role="tab" id="heading#index#"> | |
<h5 class="mb-0"> | |
<a data-toggle="collapse" class="collapsed" href="##collapse#index#" aria-expanded="true" aria-controls="collapse#index#"> | |
#file# | |
</a> | |
</h5> | |
</div> | |
<div id="collapse#index#" class="collapse hide" role="tabpanel" aria-labelledby="heading#index#" data-parent="##accordion"> | |
<div class="card-body"> | |
<table class="table"> | |
<tbody> | |
<cfloop array="#data.files[file]#" index="issue"> | |
<tr> | |
<td> | |
<cfswitch expression="#issue.severity#"> | |
<cfcase value="ERROR"><span class="oi" data-glyph="bug"></span></cfcase> | |
<cfcase value="WARNING"><span class="oi" data-glyph="warning"></span></cfcase> | |
<cfdefaultcase><span class="oi" data-glyph="info"></span></cfdefaultcase> | |
</cfswitch> | |
</td> | |
<td>#issue.id#</td> | |
<td>#issue.message#</td> | |
<!--- <td>[#issue.line#,#issue.column#]</td> ---> | |
<td> | |
<button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="##expressionModal" data-issue="#encodeForHTMLAttribute(serializeJSON({'id' = issue.id, 'message' = issue.message, 'line' = issue.line}))#" data-file="#listLast(file,"\")#" data-expression="#encodeForHTML(issue.expression)#"> | |
[#issue.line#,#issue.column#] | |
</button> | |
</td> | |
</tr> | |
</cfloop> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
<cfset index++> | |
</cfloop> | |
</div> | |
</div> | |
</cfoutput> | |
<div class="modal fade" id="expressionModal" tabindex="-1" role="dialog" aria-hidden="true"> | |
<div class="modal-dialog" role="document"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h5 class="modal-title"></h5> | |
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |
<span aria-hidden="true">×</span> | |
</button> | |
</div> | |
<div class="modal-body"> | |
<h6 class="issue-id"></h6> | |
<p class="issue-msg"></p> | |
<p class="issue-line"></p> | |
<pre></pre> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Optional JavaScript --> | |
<!-- jQuery first, then Popper.js, then Bootstrap JS --> | |
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script> | |
<script> | |
$(function () { | |
//$('[data-toggle="popover"]').popover({html: true}); | |
$('#expressionModal').on('show.bs.modal', function (event) { | |
var button = $(event.relatedTarget); // Button that triggered the modal | |
var expression = button.data('expression'); | |
var issue = button.data('issue'); | |
var file = button.data('file'); | |
var modal = $(this); | |
modal.find('pre').text( expression ); | |
modal.find('.issue-id').text( issue.id ); | |
modal.find('.issue-msg').text( issue.message ); | |
modal.find('.issue-line').text( "Line: " + issue.line ); | |
modal.find('.modal-title').text( file ); | |
}) | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment