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

feat: add sarif formatter #530

Merged
merged 3 commits into from
Dec 21, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Linter for Solidity programming language
Options:

-V, --version output the version number
-f, --formatter [name] report formatter name (stylish, table, tap, unix, json, compact)
-f, --formatter [name] report formatter name (stylish, table, tap, unix, json, compact, sarif)
-w, --max-warnings [maxWarningsNumber] number of allowed warnings
-c, --config [file_name] file to use as your .solhint.json
-q, --quiet report errors only - default: false
Expand Down
334 changes: 334 additions & 0 deletions e2e/formatters-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const fs = require('fs-extra')
const os = require('os')
const path = require('path')
const shell = require('shelljs')
const url = require('url')

function useFixture(dir) {
beforeEach(`switch to ${dir}`, function () {
Expand Down Expand Up @@ -344,5 +345,338 @@ describe('e2e', function () {
expect(code).to.equal(1)
})
})

describe('sarif formatter tests', () => {
const formatterType = 'sarif'

it('should always output with correct SARIF version and tool metadata', () => {
const { code, stdout } = shell.exec(`solhint -f ${formatterType}`)
const sarifOutput = JSON.parse(stdout)

expect(code).to.equal(0)
expect(sarifOutput['$schema']).to.eq('http://json.schemastore.org/sarif-2.1.0-rtm.5')
expect(sarifOutput.version).to.eq('2.1.0')
expect(sarifOutput.runs[0].tool.driver.name).to.eq('solhint')
expect(sarifOutput.runs[0].tool.driver.informationUri).to.eq('https://github.com/protofire/solhint')
expect(sarifOutput.runs[0].tool.driver.version).to.eq(require('../package.json').version)
})

it('should output with empty results and no artifacts when file does not exist and sarif is the formatter', () => {
const { code, stdout } = shell.exec(`solhint ${PATH}contracts/Foo1.sol -f ${formatterType}`)
const sarifOutput = JSON.parse(stdout)

expect(code).to.equal(0)
expect(sarifOutput.runs[0].artifacts).to.be.undefined
expect(sarifOutput.runs[0].results).to.be.empty
})

it('should output with sarif formatter for Foo2', () => {
const { code, stdout } = shell.exec(`solhint ${PATH}contracts/Foo2.sol -f ${formatterType}`)
const sarifOutput = JSON.parse(stdout)

const expectedUriPath = url.pathToFileURL(`${PATH}contracts/Foo2.sol`).toString()
const expectedResults = [
{
level: "warning",
message: {
text: "Constant name must be in capitalized SNAKE_CASE"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPath,
index: 0
},
region: {
startLine: 5,
startColumn: 5
}
}
}
],
ruleId: "const-name-snakecase"
},
{
level: "warning",
message: {
text: "Explicitly mark visibility in function (Set ignoreConstructors to true if using solidity >=0.7.0)"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPath,
index: 0
},
region: {
startLine: 7,
startColumn: 5
}
}
}
],
ruleId: "func-visibility"
},
{
level: "warning",
message: {
text: "Code contains empty blocks"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPath,
index: 0
},
region: {
startLine: 7,
startColumn: 19
}
}
}
],
ruleId: "no-empty-blocks"
}
]

expect(code).to.equal(0)
expect(sarifOutput.runs[0].artifacts[0].location.uri).to.eq(expectedUriPath)
expect(sarifOutput.runs[0].results).to.deep.equal(expectedResults)
})


it('should output with sarif formatter for Foo and Foo2 and Foo3', () => {
const { code, stdout } = shell.exec(
`solhint ${PATH}contracts/Foo.sol ${PATH}contracts/Foo2.sol ${PATH}contracts/Foo3.sol -f ${formatterType}`
)
const sarifOutput = JSON.parse(stdout)

const expectedUriPathFoo = url.pathToFileURL(`${PATH}contracts/Foo.sol`).toString()
const expectedUriPathFoo2 = url.pathToFileURL(`${PATH}contracts/Foo2.sol`).toString()
const expectedUriPathFoo3 = url.pathToFileURL(`${PATH}contracts/Foo3.sol`).toString()
const expectedResults = [
{
level: "error",
message: {
text: "Compiler version >=0.6.0 does not satisfy the ^0.8.0 semver requirement"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo,
index: 0
},
region: {
startLine: 2,
startColumn: 1
}
}
}
],
ruleId: "compiler-version"
},
{
level: "warning",
message: {
text: "Constant name must be in capitalized SNAKE_CASE"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo,
index: 0
},
region: {
startLine: 5,
startColumn: 5
}
}
}
],
ruleId: "const-name-snakecase"
},
{
level: "warning",
message: {
text: "Explicitly mark visibility of state"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo,
index: 0
},
region: {
startLine: 6,
startColumn: 5
}
}
}
],
ruleId: "state-visibility"
},
{
level: "warning",
message: {
text: "'TEST2' should start with _"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo,
index: 0
},
region: {
startLine: 6,
startColumn: 5
}
}
}
],
ruleId: "private-vars-leading-underscore"
},
{
level: "warning",
message: {
text: "Variable name must be in mixedCase"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo,
index: 0
},
region: {
startLine: 6,
startColumn: 5
}
}
}
],
ruleId: "var-name-mixedcase"
},
{
level: "warning",
message: {
text: "Explicitly mark visibility in function (Set ignoreConstructors to true if using solidity >=0.7.0)"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo,
index: 0
},
region: {
startLine: 8,
startColumn: 5
}
}
}
],
ruleId: "func-visibility"
},
{
level: "warning",
message: {
text: "Code contains empty blocks"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo,
index: 0
},
region: {
startLine: 8,
startColumn: 19
}
}
}
],
ruleId: "no-empty-blocks"
},
{
level: "warning",
message: {
text: "Constant name must be in capitalized SNAKE_CASE"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo2,
index: 1
},
region: {
startLine: 5,
startColumn: 5
}
}
}
],
ruleId: "const-name-snakecase"
},
{
level: "warning",
message: {
text: "Explicitly mark visibility in function (Set ignoreConstructors to true if using solidity >=0.7.0)"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo2,
index: 1
},
region: {
startLine: 7,
startColumn: 5
}
}
}
],
ruleId: "func-visibility"
},
{
level: "warning",
message: {
text: "Code contains empty blocks"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: expectedUriPathFoo2,
index: 1
},
region: {
startLine: 7,
startColumn: 19
}
}
}
],
ruleId: "no-empty-blocks"
}
]

// There's an error, that is why exit code is 1
expect(code).to.equal(1)
expect(sarifOutput.runs[0].artifacts[0].location.uri).to.eq(expectedUriPathFoo)
expect(sarifOutput.runs[0].artifacts[1].location.uri).to.eq(expectedUriPathFoo2)
expect(sarifOutput.runs[0].artifacts[2].location.uri).to.eq(expectedUriPathFoo3)
expect(sarifOutput.runs[0].results).to.deep.equal(expectedResults)
})

})
})
})
1 change: 1 addition & 0 deletions lib/formatters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ files in this directory are pulled from eslint repository:
- stylish.js: eslint - Sindre Sorhus
- json.js: eslint - Artur Lukianov
- compact.js: eslint - Nicholas C. Zakas
- sarif.js: [@microsoft/eslint-formatter-sarif](https://www.npmjs.com/package/@microsoft/eslint-formatter-sarif)

Loading
Loading