solhint
Version:
Solidity Code Linter
265 lines (233 loc) • 9.05 kB
JavaScript
/**
* @fileoverview SARIF v2.1 formatter
* @author Adapted for solhint by @eshaan7 (Original: Microsoft <https://www.npmjs.com/package/@microsoft/eslint-formatter-sarif>)
*/
const url = require('url')
const lodash = require('lodash')
//------------------------------------------------------------------------------
// Helper Functions
//------------------------------------------------------------------------------
/**
* Returns the version of used solhint package
* @returns {string} solhint version or undefined
* @private
*/
function getSolhintVersion() {
try {
// Resolve solhint relative to main entry script, not the formatter
const solhintPackageJson = require('../../package.json')
return solhintPackageJson.version
} catch {
// Formatter was not called from solhint, return undefined
}
}
/**
* Returns the severity of warning or error
* @param {Object} message message object to examine
* @returns {string} severity level
* @private
*/
function getResultLevel(message) {
if (message.fatal || message.severity === 2) {
return 'error'
}
return 'warning'
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function (results, data) {
const rulesMeta = lodash.get(data, 'rulesMeta', null)
const sarifLog = {
version: '2.1.0',
$schema: 'http://json.schemastore.org/sarif-2.1.0-rtm.5',
runs: [
{
tool: {
driver: {
name: 'solhint',
informationUri: 'https://github.com/protofire/solhint',
rules: [],
},
},
},
],
}
const solhintVersion = getSolhintVersion()
if (typeof solhintVersion !== 'undefined') {
sarifLog.runs[0].tool.driver.version = solhintVersion
}
const sarifFiles = {}
const sarifArtifactIndices = {}
let nextArtifactIndex = 0
const sarifRules = {}
const sarifRuleIndices = {}
let nextRuleIndex = 0
const sarifResults = []
// Emit a tool configuration notification with this id if ESLint emits a message with
// no ruleId (which indicates an internal error in ESLint).
//
// It is not clear whether we should treat these messages tool configuration notifications,
// tool execution notifications, or a mixture of the two, based on the properties of the
// message. https://github.com/microsoft/sarif-sdk/issues/1798, "ESLint formatter can't
// distinguish between an internal error and a misconfiguration", tracks this issue.
const internalErrorId = 'ESL0999'
const toolConfigurationNotifications = []
let executionSuccessful = true
for (const result of results) {
// Only add it if not already there.
if (typeof sarifFiles[result.filePath] === 'undefined') {
sarifArtifactIndices[result.filePath] = nextArtifactIndex++
// Create a new entry in the files dictionary.
sarifFiles[result.filePath] = {
location: {
uri: url.pathToFileURL(result.filePath),
},
}
const containsSuppressedMessages =
result.suppressedMessages && result.suppressedMessages.length > 0
const messages = containsSuppressedMessages
? [...result.messages, ...result.suppressedMessages]
: result.messages
if (messages.length > 0) {
for (const message of messages) {
const sarifRepresentation = {
level: getResultLevel(message),
message: {
text: message.message,
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: url.pathToFileURL(result.filePath),
index: sarifArtifactIndices[result.filePath],
},
},
},
],
}
if (message.ruleId) {
sarifRepresentation.ruleId = message.ruleId
if (rulesMeta && typeof sarifRules[message.ruleId] === 'undefined') {
const meta = rulesMeta[message.ruleId]
// An unknown ruleId will return null. This check prevents unit test failure.
if (meta) {
sarifRuleIndices[message.ruleId] = nextRuleIndex++
if (meta.docs) {
// Create a new entry in the rules dictionary.
sarifRules[message.ruleId] = {
id: message.ruleId,
helpUri: meta.docs.url,
properties: {
category: meta.docs.category,
},
}
if (meta.docs.description) {
sarifRules[message.ruleId].shortDescription = {
text: meta.docs.description,
}
}
// Some rulesMetas do not have docs property
} else {
sarifRules[message.ruleId] = {
id: message.ruleId,
helpUri: 'Please see details in message',
properties: {
category: 'No category provided',
},
}
}
}
}
if (sarifRuleIndices[message.ruleId] !== 'undefined') {
sarifRepresentation.ruleIndex = sarifRuleIndices[message.ruleId]
}
if (containsSuppressedMessages) {
sarifRepresentation.suppressions = message.suppressions
? message.suppressions.map((suppression) => {
return {
kind: suppression.kind === 'directive' ? 'inSource' : 'external',
justification: suppression.justification,
}
})
: []
}
} else {
// ESLint produces a message with no ruleId when it encounters an internal
// error. SARIF represents this as a tool execution notification rather
// than as a result, and a notification has a descriptor.id property rather
// than a ruleId property.
sarifRepresentation.descriptor = {
id: internalErrorId,
}
// As far as we know, whenever ESLint produces a message with no rule id,
// it has severity: 2 which corresponds to a SARIF error. But check here
// anyway.
if (sarifRepresentation.level === 'error') {
// An error-level notification means that the tool failed to complete
// its task.
executionSuccessful = false
}
}
if (message.line > 0 || message.column > 0) {
sarifRepresentation.locations[0].physicalLocation.region = {}
if (message.line > 0) {
sarifRepresentation.locations[0].physicalLocation.region.startLine = message.line
}
if (message.column > 0) {
sarifRepresentation.locations[0].physicalLocation.region.startColumn = message.column
}
if (message.endLine > 0) {
sarifRepresentation.locations[0].physicalLocation.region.endLine = message.endLine
}
if (message.endColumn > 0) {
sarifRepresentation.locations[0].physicalLocation.region.endColumn = message.endColumn
}
}
if (message.source) {
// Create an empty region if we don't already have one from the line / column block above.
sarifRepresentation.locations[0].physicalLocation.region =
sarifRepresentation.locations[0].physicalLocation.region || {}
sarifRepresentation.locations[0].physicalLocation.region.snippet = {
text: message.source,
}
}
if (message.ruleId) {
sarifResults.push(sarifRepresentation)
} else {
toolConfigurationNotifications.push(sarifRepresentation)
}
}
}
}
}
if (Object.keys(sarifFiles).length > 0) {
sarifLog.runs[0].artifacts = []
for (const path of Object.keys(sarifFiles)) {
sarifLog.runs[0].artifacts.push(sarifFiles[path])
}
}
// Per the SARIF spec §3.14.23, run.results must be present even if there are no results.
// This provides a positive indication that the run completed and no results were found.
sarifLog.runs[0].results = sarifResults
if (toolConfigurationNotifications.length > 0) {
sarifLog.runs[0].invocations = [
{
toolConfigurationNotifications,
executionSuccessful,
},
]
}
if (Object.keys(sarifRules).length > 0) {
for (const ruleId of Object.keys(sarifRules)) {
const rule = sarifRules[ruleId]
sarifLog.runs[0].tool.driver.rules.push(rule)
}
}
return JSON.stringify(
sarifLog,
null, // replacer function
2 // # of spaces for indents
)
}