Skip to content

Instantly share code, notes, and snippets.

@jsteinshouer
Created December 28, 2017 17:24
Show Gist options
  • Save jsteinshouer/9e3556e5940f86388f9ecd91d129b78d to your computer and use it in GitHub Desktop.
Save jsteinshouer/9e3556e5940f86388f9ecd91d129b78d to your computer and use it in GitHub Desktop.
Example Running CFLint with CommandBox Task Runner
/**
*
* 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();
}
}
}
<!---
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">&times;</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