Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@W-11119708@ Adding SFG Engine #673

Merged
merged 9 commits into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ version: 2.1
defaults: &defaults
working_directory: ~/repo
docker:
- image: cimg/openjdk:17.0-node
- image: cimg/openjdk:8.0-node

orbs:
win: circleci/windows@2.2.0 # The Windows orb give you everything you need to start using the Windows executor.
Expand Down
334 changes: 334 additions & 0 deletions html-templates/dfa-simple.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/rowgroup/1.1.2/css/rowGroup.dataTables.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.21/css/dataTables.bootstrap4.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/rowgroup/1.1.2/css/rowGroup.bootstrap4.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.css">
<style>
/* Move the search box to the left */
.dataTables_filter {
float: left !important;
padding-left: 10px;
padding-top: 10px;
}

/* Datatable should take up entire width */
.table {
width:100% !important;
}

/* Datatable column */
.scannerSeverity {
width: 10px;
text-align: center;
}

/* Datatable column. Right justify numerics. */
.scannerLocation {
text-align: right;
}

.summaryChartCell {
background-color:white;
}

.summaryChartContainer {
padding-left:10%;
padding-right:10%;
width:100%;
background-color:white
}

h1, h4 {
text-align: center;
}

#footer {
padding-top: 10px;
padding-bottom: 10px;
background-color: #e0e0e0;
font-size: 1.5em;
text-align: center;
}

#reportTitle {
margin: 25px;
}

#summaryFiles {
margin-top: 25px;
}
</style>
<script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script type="text/javascript" language="javascript"
src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/rowgroup/1.1.2/js/dataTables.rowGroup.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
<script type="text/javascript" class="init">

// BEGIN - Placeholders filled in by the scanner
const violations = {{{violations}}};
// END - Placeholders filled in by the scanner

// Global DataTable displayed in the report
var dataTable = null;

// Color if the severity is greater than 5
const unknownSeverityColor = '#cccccc';

// Background colors for severity
// Palette from https://www.w3schools.com/colors/colors_palettes.asp
// TODO: Accessibility
function getSeverityColor(severity) {
switch (parseInt(severity)) {
case 1:
return '#d96459';
case 2:
return '#f2ae72';
case 3:
return '#f2e394';
case 4:
return '#588c7e';
case 5:
return '#96ceb4';
default:
return '#cccccc';
}
}

// Convert a map of severity->count to a structure consumable by the chart library
// options.labelFormatter: function(severity, count) that returns a label
function sevMapToChartData(sevMap, options) {
var dataset = {
data: [],
backgroundColor: []
};

var dataLabels = [];
// Sort the entries by key order
sevMap = new Map([...sevMap.entries()].sort());
for (var severity of sevMap.keys()) {
var count = sevMap.get(severity);
dataset.data.push(count);
dataset.backgroundColor.push(getSeverityColor(severity));
if (options && options.labelFormatter) {
dataLabels.push(options.labelFormatter(severity, count));
} else {
dataLabels.push(`Sev${severity}`);
}
}

return {
datasets: [dataset],
labels: dataLabels
};
}

// Creates a horizontal bar chart below a grouping of rows. Called
// asynchronously as the rows are added/removed by the search feature.
function populateRowGroupChart(id, summary) {
const ctx = $(`#${id}`);
if (!ctx.length) {
// This can happen when the user quickly types in the search field
return;
}

const data = sevMapToChartData(summary);

new Chart(ctx, {
type: 'horizontalBar',
data: data,
options: {
aspectRatio: 20,
animation: {
duration: 0
},
legend: {
display: false
},
scales: {
xAxes: [{
ticks: { beginAtZero: true }
}]
}
},
});
}

function populateSummaryChart() {
// The number of files is determined by finding the number of row groupings
const numFiles = $('.dtrg-start').length;
var text = `${numFiles} File`;
if (numFiles !== 1) {
text += 's'
}
$('#summaryFiles').text(text);

// Number/type of violations is found by analyzing the visible data rows
var summary = new Map();
dataTable.rows({ search: 'applied' })
.data()
.pluck('severity')
.each(function (value, index) {
var count = summary.get(value);
count = count ? (count + 1) : 1;
summary.set(value, count);
});

// Calculate the total number of violations, independent of severity
const numViolations = Array.from(summary.values()).reduce((a, b) => a + b, 0);
text = `${numViolations} Violation`;
if (numViolations !== 1) {
text += 's'
}
$('#summaryViolations').text(text);

const data = sevMapToChartData(summary, {
labelFormatter: function(severity, count) {
return `Sev${severity} (${count})`;
}
});

// Create a new canvas, remove the previous canvas
const canvas = $('<canvas/>');
$('#summaryChart').empty().append(canvas);

new Chart(canvas, {
type: 'doughnut',
data: data,
options: {
aspectRatio: 6,
animation: {
duration: 0
},
legend: {
display: true,
position: 'bottom',
// Prevent a click from modifying the chart
onClick: (e) => e.stopPropagation(),
labels: {
fontSize: 18,
fontStyle: 'bold'
}
}
},
});
}

// Create a table where the first column(fileName) is hidden but used for grouping.
// Initial sort by fileName, severity, lineNumber
// fileName is used in orderFixed to force all results to sort underneath the fileName grouping
var rowSummaryIdCounter = 0;
$(document).ready(function () {
dataTable = $('#violations').DataTable({
columnDefs: [
{ 'visible': false, 'targets': [0] }
],
rowGroup: {
dataSrc: ['fileName'],
startRender: function (rows, group, level) {
return $('<tr>' +
`<td colspan="10">${group}</td>` +
'</tr>'
);
},
endRender: function (rows, group, level) {
var summary = new Map();

rows.data().pluck('severity').each( function(value, index) {
var count = summary.get(value);
count = count ? (count + 1) : 1;
summary.set(value, count);
});

const id = `summaryGroup${rowSummaryIdCounter++}`;
window.setTimeout(function() {
populateRowGroupChart(id, summary);
}, 0);
return $(
'<tr>' +
`<td colspan="10" class="summaryChartCell"><div class="summaryChartContainer"><canvas id="${id}"></canvas><div></td>` +
'</tr>');
}
},
orderFixed: [[0, 'asc']],
order: [[1, 'asc'], [6, 'asc']],
data: violations,
paging: false,
columns: [
{ data: 'fileName' },
{ data: 'severity', className: 'scannerSeverity' },
{ data: 'engine' },
{ data: 'category' },
{ data: 'ruleName',
// Convert the ruleName to a hyperlink that displays the rule information
'render': function(data, type, row, meta){
if(data && type === 'display'){
data = '<a href="' + row.url + '" target="__blank">' + data + '</a>';
}
return data;
}
},
{ data: 'message' },
// 'defaultContent' provides information to the Datatables code in cases where the row
// corresponds to something non file location specific. i.e. a tsconfig.json error
{ data: 'line', className: 'scannerLocation', defaultContent: ''},
{ data: 'column', className: 'scannerLocation', defaultContent: ''},
{ data: 'sinkFileName', className: 'scannerLocation', defaultContent: ''},
{ data: 'sinkLine', className: 'scannerLocation', defaultContent: ''},
{ data: 'sinkColumn', className: 'scannerLocation', defaultContent: ''}
],
'rowCallback': function(row, data, index) {
var severity = data['severity'];
var bgColor = getSeverityColor(severity);
if (bgColor) {
$(row).find('td:eq(0)').css('background-color', bgColor);
}
},
'infoCallback': function( settings, start, end, max, total, pre ) {
window.setTimeout(function() {
populateSummaryChart();
}, 0);
}
});
});
</script>
</head>

<body>
<h1 id="reportTitle">Salesforce CLI Scanner Report</h1>
<div id="summaryChart"/></div>
<h4 id="summaryFiles"></h4>
<h4 id="summaryViolations"></h4>
<div class="fw-container">
<div class="fw-body">
<div class="content">
<table id="violations" class="table table-striped table-bordered">
<thead>
<tr>
<th>File Name</th>
<th>Sev</th>
<th>Engine</th>
<th>Category</th>
<th>Rule Name</th>
<th>Message</th>
<th>Line</th>
<th>Column</th>
<th>Sink File</th>
<th>Sink Line</th>
<th>Sink Column</th>
</tr>
</thead>
<tbody/>
</table>
</div>
</div>
</div>
<div>
<div id="footer">This report was generated with command <b><i>{{{commandLine}}}</i></b> from working directory <b><i>{{{workingDirectory}}}</i></b></div>
</div>
</body>

</html>
50 changes: 50 additions & 0 deletions messages/run-dfa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module.exports = {
"commandDescription": "evaluate a selection of DFA rules against a codebase",
"commandDescriptionLong": `Scan codebase with all DFA rules by default.
You can choose the format of output and decide between printing the results directly
or as contents of a file that you provide with --outfile flag.`,
"flags": {
"formatDescription": "format of results",
"formatDescriptionLong": "Specifies output format with results written directly to the console.",
"ignoreparseerrorsDescription": "ignore compilation failures in scanned files",
"ignoreparseerrorsDescriptionLong": "ignore compilation failures in scanned files. Inherits value from SFGE_IGNORE_PARSE_ERRORS env-var if set.",
"normalizesevDescription": "A normalized severity 1 (high), 2 (moderate), and 3 (low) is returned in addition to the engine specific severity",
"normalizesevDescriptionLong": "A normalized severity 1 (high), 2 (moderate), and 3 (low) is returned in addition to the engine specific severity. For the html option, the normalized severity is displayed instead of the engine severity",
"outfileDescription": "location of output file",
"outfileDescriptionLong": "Write output to a file.",
"projectdirDescription": "Root project directory",
"projectdirDescriptionLong": "Root project directory. Must be paths, not globs. Multiple values can be specified as a comma-separated list",
"rulethreadcountDescription": "Number of threads evaluating rules (default is 4)",
"rulethreadcountDescriptionLong": "Specify number of rule evaluation threads, i.e. how many entrypoints can be evaluated concurrently. Default is 4. Inherits value from SFGE_RULE_THREAD_COUNT env-var if set.",
"rulethreadtimeoutDescription": "Timeout for individual rule threads, in milliseconds (default is 900,000 ms)",
"rulethreadtimeoutDescriptionLong": "Time limit for evaluating a single entrypoint. Value in milliseconds. Inherits from SFGE_RULE_THREAD_TIMEOUT env-var if set. Default is 900,000 ms, or 15 minutes.",
"sevthresholdDescription": "throws an error when violations of specific severity (or more severe) are detected, invokes --normalize-severity",
"sevthresholdDescriptionLong": "Throws an error if violations are found with equal or greater severity than provided value. Values are 1 (high), 2 (moderate), and 3 (low). Exit code is the most severe violation. Using this flag also invokes the --normalize-severity flag",
"targetDescription": "location of source code",
"targetDescriptionLong": "Source code location. May use glob patterns. Multiple values can be specified as a comma-separated list"
},
"validations": {
"projectdirCannotBeGlob": "--projectdir cannot specify globs",
"projectdirMustBeDir": "--projectdir must specify directories",
"projectdirMustExist": "--projectdir must specify existing paths"
},
"examples": `The paths specified for --projectdir must cumulatively contain all files specified through --target.
Good: $ sfdx scanner:run:dfa --target "./myproject/main/default/classes/*.cls" --projectdir "./myproject/"
Good: $ sfdx scanner:run:dfa --target "./**/*.cls" --projectdir "./"
Good: $ sfdx scanner:run:dfa --target "./dir1/file1.cls,./dir2/file2.cls" --projectdir "./dir1/,./dir2/"
Bad: $ sfdx scanner:run:dfa --target "./**/*.cls" --projectdir "./myproject"
Wrap globs in quotes.
Unix example: $ sfdx scanner:run:dfa --target './**/*.cls,!./**/IgnoreMe.cls' ...
Windows example: > sfdx scanner:run:dfa --target ".\\**\\*.cls,!.\\**\\IgnoreMe.cls" ...
Evaluate rules against all .cls files below the current directory, except for IgnoreMe.cls.
Use --normalize-severity to output a normalized (across all engines) severity (1 [high], 2 [moderate], and 3 [low]) in addition to the engine specific severity (when shown).
E.g., $ sfdx scanner:run:dfa --target "/some-project/" --projectdir "/some-project/" --format csv --normalize-severity
Use --severity-threshold to throw a non-zero exit code when rule violations of a specific normalized severity (or greater) are found. For this example, if there are any rule violations with a severity of 2 or more (which includes 1-high and 2-moderate), the exit code will be equal to the severity of the most severe violation.
E.g., $ sfdx scanner:run:dfa --target "/some-project/" --projectdir "/some-project/" --severity-threshold 2
Use --rule-thread-count to allow more (or fewer) entrypoints to be evaluated concurrently.
E.g., $ sfdx scanner:run:dfa --rule-thread-count 6
Use --rule-thread-timeout to increase (or decrease) the maximum runtime for a single entrypoint evaluation.
E.g., $ sfdx scanner:run:dfa --rule-thread-timeout 9000000 ...
Increases timeout from 15 minutes (default) to 150 minutes.
`
};