Merge pull request #3642 from ethereum/solhint-linter-plugin

Solhint linter plugin
pull/3773/head
Joseph Izang 1 year ago committed by GitHub
commit 2f9f862ef3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      apps/remix-ide-e2e/src/tests/contract_flattener.test.ts
  2. 11
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  3. 2
      apps/remix-ide/project.json
  4. 12
      apps/remix-ide/src/remixAppManager.js
  5. 16
      apps/solhint/package.json
  6. 70
      apps/solhint/project.json
  7. 8
      apps/solhint/src/app/App.tsx
  8. 145
      apps/solhint/src/app/SolhintPluginClient.ts
  9. BIN
      apps/solhint/src/favicon.ico
  10. 13
      apps/solhint/src/index.html
  11. 10
      apps/solhint/src/main.tsx
  12. 20
      apps/solhint/src/profile.json
  13. 23
      apps/solhint/tsconfig.app.json
  14. 16
      apps/solhint/tsconfig.json
  15. 60
      apps/solhint/webpack.config.js
  16. 517
      apps/solhint/yarn.lock
  17. 9
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  18. 11
      libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
  19. 198
      libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
  20. 44
      libs/remix-ui/static-analyser/src/lib/components/BasicTitle.tsx
  21. 15
      libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts
  22. 672
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  23. 138
      libs/remix-ui/static-analyser/src/staticanalyser.d.ts
  24. 6
      libs/remix-ui/vertical-icons-panel/src/lib/components/Badge.tsx
  25. 3
      libs/remix-ui/vertical-icons-panel/src/lib/components/IconList.tsx

@ -9,8 +9,9 @@ module.exports = {
'@sources': () => sources,
'Should flatten contract after creation': function (browser: NightwatchBrowser) {
browser.addFile('TestContract.sol', sources[0]['TestContract.sol'])
.pause(10000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemTestContract.sol"]')
.pause(7000)
.pause(3000)
.click('*[data-id="treeViewLitreeViewItemTestContract.sol"]')
.rightClick('*[data-id="treeViewLitreeViewItemTestContract.sol"]')
.click('*[id="menuitemflattenacontract"]')

@ -37,19 +37,24 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.clickLaunchIcon('solidity')
.click('*[id="compileBtn"]')
.pause(10000)
.clickLaunchIcon('solidityStaticAnalysis')
.click('*[id="staticAnalysisRunBtn"]')
.waitForElementPresent('#staticanalysisresult .warning', 5000)
.assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '1') // Check warning count
// Check warning count
.click('*[data-rb-event-key="basic"]')
.assert.containsText('*[data-id="StaticAnalysisErrorCount"]', '1')
.verify.elementPresent('input[name="showLibWarnings"]')
.verify.not.elementPresent('input[name="showLibWarnings"]:checked')
.verify.elementPresent('label[id="headingshowLibWarnings"]')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '382')
.click('*[data-rb-event-key="basic"]')
.assert.containsText('*[data-id="StaticAnalysisErrorCount"]', '382')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '1')
.assert.containsText('*[data-id="StaticAnalysisErrorCount"]', '1')
.end()
}
}

@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/remix-ide/src",
"projectType": "application",
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "walletconnect"],
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect"],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",

@ -17,7 +17,7 @@ const requiredModules = [ // services + layout views + system views
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
const loadLocalPlugins = ["doc-gen", "doc-viewer", "etherscan", "vyper", "walletconnect"]
const loadLocalPlugins = ["doc-gen", "doc-viewer", "etherscan", "vyper", 'solhint', 'walletconnect']
const sensitiveCalls = {
'fileManager': ['writeFile', 'copyFile', 'rename', 'copyDir'],
@ -228,6 +228,16 @@ export class RemixAppManager extends PluginManager {
sticky: true,
group: 7
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'solhint',
name: 'lintContractCustomAction',
label: 'Lint Contract',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true
})
}
}

@ -0,0 +1,16 @@
{
"name": "solhint",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"resolutions": {
"antlr4": "4.11"
},
"dependencies": {
"solhint": "^3.4.1"
},
"devDependencies": {
"@remixproject/plugin": "^0.3.31",
"@remixproject/plugin-webview": "^0.3.31"
}
}

@ -0,0 +1,70 @@
{
"name": "solhint",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/solhint/src",
"projectType": "application",
"implicitDependencies": [],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": [
"{options.outputPath}"
],
"dependsOn": ["install"],
"defaultConfiguration": "development",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/solhint",
"index": "apps/solhint/src/index.html",
"baseHref": "./",
"main": "apps/solhint/src/main.tsx",
"tsConfig": "apps/solhint/tsconfig.app.json",
"assets": [
"apps/solhint/src/favicon.ico",
"apps/solhint/src/profile.json"
],
"styles": [],
"webpackConfig": "apps/solhint/webpack.config.js"
},
"configurations": {
"development": {},
"production": {
"fileReplacements": [
{
"replace": "apps/solhint/src/environments/environment.ts",
"with": "apps/solhint/src/environments/environment.prod.ts"
}
]
}
}
},
"install": {
"executor": "nx:run-commands",
"options": {
"commands": [
"cd apps/solhint && yarn"
],
"parallel": false
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "solhint:build",
"hmr": true,
"baseHref": "/"
},
"configurations": {
"development": {
"buildTarget": "solhint:build:development",
"port": 7003
},
"production": {
"buildTarget": "solhint:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,8 @@
import React, { useEffect, useState } from "react";
import { SolHint } from "./SolhintPluginClient";
const client = new SolHint();
export default function App() {
return <></>;
}

@ -0,0 +1,145 @@
import { PluginClient } from '@remixproject/plugin'
import { createClient } from '@remixproject/plugin-webview'
import EventEmitter from 'events'
import { processStr } from 'solhint'
import { applyExtends } from 'solhint/lib/config/config-file'
import bestPractises from 'solhint/lib/rules/best-practises'
import naming from 'solhint/lib/rules/naming'
import order from 'solhint/lib/rules/order'
import security from 'solhint/lib/rules/security'
import deprecations from 'solhint/lib/rules/deprecations'
import miscellaneous from 'solhint/lib/rules/miscellaneous'
import { customAction } from '@remixproject/plugin-api'
type Report = {
line: number,
column: number,
severity: string,
message: string,
ruleId: string,
fix: string
}
const Config = `{
"extends": "solhint:recommended",
"plugins": [],
"rules": {
"avoid-suicide": "error",
"avoid-sha3": "warn"
}
}`
export class SolHint extends PluginClient {
triggerLinter: boolean
constructor() {
super()
this.methods = ['lintContract', 'lintOnCompilation', 'lintContractCustomAction', 'lint']
createClient(this)
this.onload().then(async () => {
await this.lintOnCompilation()
})
}
async createConfigFile () {
await this.call('fileManager', 'writeFile', '.solhint.json', Config)
}
async lintOnCompilation() {
if(!this.triggerLinter) return
this.on('solidity', 'compilationFinished', async (fileName, source, languageVersion, data) => {
const hints = await this.lint(fileName)
console.log('after compile', { hints })
this.emit('lintOnCompilationFinished', hints)
})
this.triggerLinter = false
}
/**
* method to handle context menu action in file explorer for
* solhint plugin
* @param action interface CustomAction
*/
async lintContractCustomAction(action: customAction) {
this.triggerLinter = true
await this.call('solidity', 'compile', action.path[0])
await this.lintContract(action.path[0])
}
async lintContract(file: string) {
const hints = await this.lint(file)
console.log({ hints })
this.emit('lintingFinished', hints)
}
public async lint(fileName: string) {
const content = await this.call('fileManager', 'readFile', fileName)
let configContent = Config
if (await this.call('fileManager' as any, 'exists', '.solhint.json')) {
configContent = await this.call('fileManager', 'readFile', '.solhint.json')
}
const configContentObj = JSON.parse(configContent)
// apply the extend property
const rulesObj = applyExtends(configContentObj, (path) => this.rules[path]())
configContentObj.rules = { ...rulesObj, ...configContentObj.rules }
configContentObj.extends = []
const reporters = processStr(content, configContentObj)
const reports: Array<Report> = reporters.reports
const hints = reports.map((report: Report) => {
return {
formattedMessage: `${report.message}\n${report.fix ? report.fix : ''}`,
type: this.severity[report.severity] || 'error',
column: report.column,
line: report.line - 1
}
})
return hints
}
severity = {
2: 'error',
3: 'warning'
}
rules = {
'solhint:recommended': () => {
const enabledRules = {}
this.coreRules().forEach(rule => {
if (!rule.meta.deprecated && rule.meta.recommended) {
enabledRules[rule.ruleId] = rule.meta.defaultSetup
}
})
return enabledRules
},
'solhint:all': () => {
const enabledRules = {}
this.coreRules().forEach(rule => {
if (!rule.meta.deprecated) {
enabledRules[rule.ruleId] = rule.meta.defaultSetup
}
})
return enabledRules
},
'solhint:default': () => {
const enabledRules = {}
this.coreRules().forEach(rule => {
if (!rule.meta.deprecated && rule.meta.isDefault) {
enabledRules[rule.ruleId] = rule.meta.defaultSetup
}
})
return enabledRules
}
}
coreRules() {
return [
...bestPractises(),
...deprecations(),
...miscellaneous(),
...naming(),
...order(),
...security()
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Solhint</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app/App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)

@ -0,0 +1,20 @@
{
"name": "solhint",
"displayName": "Solhint Linter",
"description": "Linter for Solidity",
"version": "0.1.0",
"events": ["lintOnCompilationFinished", "lintingFinished"],
"methods": [
"lintWithoutCompilationCustomAction",
"lintOnCompilation",
"lintContractCustomAction"
],
"kind": "none",
"icon": "https://raw.githubusercontent.com/protofire/solhint/master/solhint-icon.png",
"location": "hiddenPanel",
"url": "",
"documentation": "https://remix-plugins.readthedocs.io/en/latest/",
"repo": "https://github.com/ethereum/remix-project",
"maintainedBy": "Remix",
"authorContact": "remix@ethereum.org"
}

@ -0,0 +1,23 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

@ -0,0 +1,60 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
// Update the webpack config as needed here.
config.resolve.fallback = {
...config.resolve.fallback,
"path": false,
"os": false,
"fs": false,
"module": false,
}
// add public path
config.output.publicPath = '/'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
}),
new webpack.DefinePlugin({
"BROWSER": true,
}),
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 2015,
compress: false,
mangle: false,
format: {
comments: false,
},
},
extractComments: false,
}),
new CssMinimizerPlugin(),
];
return config;
})

@ -0,0 +1,517 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/code-frame@^7.0.0":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/helper-validator-identifier@^7.18.6":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
"@babel/highlight@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
dependencies:
"@babel/helper-validator-identifier" "^7.18.6"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@remixproject/plugin-api@0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.31.tgz#86e7c458c58ff200bd927fd3d642877f4b5a0013"
integrity sha512-LOJRHxORNp7zW8k7//DQz5aZ7eqB7TwhYXrvzqvaryDTvtvJGWrlTHg81hzALynaxZKEWneohxjUxKvGp/eA4g==
dependencies:
"@remixproject/plugin-utils" "0.3.31"
"@remixproject/plugin-utils@0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.31.tgz#80771e00c1a1b776432abb17b1f4b2e25600d4f6"
integrity sha512-OOAjoSd+ErBMrcNQEh80NU3BjJ9fHXuftRfy5Ul9aGXN3b1LJSNVvfrG+FxX6lpyaAK5JBj+aB9pgFozgb2wlw==
dependencies:
tslib "2.0.1"
"@remixproject/plugin-webview@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.31.tgz#e5cce7d0089439b35aee4ab2a724add1e5d36b40"
integrity sha512-8yoKwIkRi9S+rqvFShNt01FfXX0H/Fijn7UkWFWJ/V6ULcw2cw9ViCz8cYZLhNUpxqezyu/LKDQL9g1TbJJoYw==
dependencies:
"@remixproject/plugin" "0.3.31"
"@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.31"
axios "^0.21.1"
"@remixproject/plugin@0.3.31", "@remixproject/plugin@^0.3.31":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.31.tgz#b6c6b58d2c7964e37024eeca4819c70ece1f3953"
integrity sha512-9ntMU9CzStloahm/wXt4V8n64ERgJzY5nG0bzQfjnI12knrdTmUo+LC42M2xaTBDDP9CzMPdqClg7XhhRLzohA==
dependencies:
"@remixproject/plugin-api" "0.3.31"
"@remixproject/plugin-utils" "0.3.31"
events "3.2.0"
"@solidity-parser/parser@^0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.0.tgz#1fb418c816ca1fc3a1e94b08bcfe623ec4e1add4"
integrity sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==
dependencies:
antlr4ts "^0.5.0-alpha.4"
ajv@^6.12.6:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.0.1:
version "8.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
antlr4@4.11, antlr4@^4.11.0:
version "4.11.0"
resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.11.0.tgz#d7466f5044fa6e333c0ec821b30c6157f6b004ae"
integrity sha512-GUGlpE2JUjAN+G8G5vY+nOoeyNhHsXoIJwP1XF1oRw89vifA1K46T6SEkwLwr7drihN7I/lf0DIjKc4OZvBX8w==
antlr4ts@^0.5.0-alpha.4:
version "0.5.0-alpha.4"
resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a"
integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
ast-parents@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3"
integrity sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
axios@^0.21.1:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
commander@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
cosmiconfig@^8.0.0:
version "8.1.3"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.1.3.tgz#0e614a118fcc2d9e5afc2f87d53cd09931015689"
integrity sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==
dependencies:
import-fresh "^3.2.1"
js-yaml "^4.1.0"
parse-json "^5.0.0"
path-type "^4.0.0"
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
dependencies:
is-arrayish "^0.2.1"
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
events@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
follow-redirects@^1.14.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
glob@^8.0.3:
version "8.1.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^5.0.1"
once "^1.3.0"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
ignore@^5.2.4:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
lodash.truncate@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
minimatch@^5.0.1:
version "5.1.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
dependencies:
brace-expansion "^2.0.1"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
dependencies:
callsites "^3.0.0"
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
dependencies:
"@babel/code-frame" "^7.0.0"
error-ex "^1.3.1"
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
pluralize@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
prettier@^2.8.3:
version "2.8.7"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450"
integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==
punycode@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
slice-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
solhint@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/solhint/-/solhint-3.4.1.tgz#8ea15b21c13d1be0b53fd46d605a24d0b36a0c46"
integrity sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==
dependencies:
"@solidity-parser/parser" "^0.16.0"
ajv "^6.12.6"
antlr4 "^4.11.0"
ast-parents "^0.0.1"
chalk "^4.1.2"
commander "^10.0.0"
cosmiconfig "^8.0.0"
fast-diff "^1.2.0"
glob "^8.0.3"
ignore "^5.2.4"
js-yaml "^4.1.0"
lodash "^4.17.21"
pluralize "^8.0.0"
semver "^6.3.0"
strip-ansi "^6.0.1"
table "^6.8.1"
text-table "^0.2.0"
optionalDependencies:
prettier "^2.8.3"
string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
table@^6.8.1:
version "6.8.1"
resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"
integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==
dependencies:
ajv "^8.0.1"
lodash.truncate "^4.4.2"
slice-ansi "^4.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
tslib@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==

@ -12,6 +12,7 @@ export interface RemixUiCheckboxProps {
inputType?: string
name?: string
checked?: boolean
disabled?: boolean
id?: string
itemName?: string
categoryId?: string
@ -19,6 +20,7 @@ export interface RemixUiCheckboxProps {
visibility?: string
display?: string
tooltipPlacement?: Placement
optionalClassName?: string
}
export const RemixUiCheckbox = ({
@ -33,8 +35,10 @@ export const RemixUiCheckbox = ({
categoryId,
title,
visibility,
optionalClassName = '',
display = 'flex',
tooltipPlacement = 'right'
disabled = false,
tooltipPlacement = 'right',
}: RemixUiCheckboxProps) => {
const childJSXWithTooltip = (
@ -43,7 +47,7 @@ export const RemixUiCheckbox = ({
tooltipId={`${name}Tooltip`}
placement={tooltipPlacement}
>
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}>
<div className={`listenOnNetwork_2A0YE0 custom-control custom-checkbox ${optionalClassName}`} style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}>
<input
id={id}
type={inputType}
@ -52,6 +56,7 @@ export const RemixUiCheckbox = ({
name={name}
className="custom-control-input"
checked={checked}
disabled={disabled}
/>
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }}>
{name ? <div className="font-weight-bold">{itemName}</div> : ''}

@ -6,18 +6,21 @@ interface StaticAnalyserButtonProps {
buttonText: string,
disabled?: boolean,
title?: string
classList?: string
}
const StaticAnalyserButton = ({
onClick,
buttonText,
disabled,
title
title,
classList
}: StaticAnalyserButtonProps) => {
let classList = "btn btn-sm w-25 btn-primary"
classList += disabled ? " disabled" : ""
const defaultStyle = "btn btn-sm w-25 btn-primary"
const newclassList = disabled && classList.length > 0 ? `${classList} disabled` :
classList.length === 0 && disabled ? `${defaultStyle} disabled` : classList.length > 0 ? `${classList}` : defaultStyle
return (
<button className={classList} disabled={disabled} onClick={onClick}>
<button id="staticAnalysisRunBtn" className={newclassList} disabled={disabled} onClick={onClick}>
<CustomTooltip
placement="right"
tooltipId="ssaRunButtonTooltip"

@ -1,14 +1,208 @@
import { CompilationResult, SourceWithTarget } from '@remixproject/plugin-api'
import React from 'react' //eslint-disable-line
import { AnalysisTab, RemixUiStaticAnalyserReducerActionType, RemixUiStaticAnalyserState, SolHintReport, SlitherAnalysisResults } from '../../staticanalyser'
import { RemixUiStaticAnalyserProps } from '@remix-ui/static-analyser'
export const compilation = (analysisModule, dispatch) => {
/**
*
* @param analysisModule { AnalysisTab } AnalysisTab ViewPlugin
* @param dispatch { React.Dispatch<any> } analysisReducer function's dispatch method
*/
export const compilation = (analysisModule: AnalysisTab,
dispatch: React.Dispatch<RemixUiStaticAnalyserReducerActionType>) => {
if (analysisModule) {
analysisModule.on(
'solidity',
'compilationFinished',
(file, source, languageVersion, data, input, version) => {
(file: string, source: SourceWithTarget, languageVersion: string, data: CompilationResult, input: string, version: string) => {
if (languageVersion.indexOf('soljson') !== 0) return
dispatch({ type: 'compilationFinished', payload: { file, source, languageVersion, data, input, version } })
}
)
}
}
/**
* Run the analysis on the currently compiled contract
* @param lastCompilationResult
* @param lastCompilationSource
* @param currentFile {string} current file path
* @param state { RemixUiStaticAnalyserState}
* @param props {RemixUiStaticAnalyserProps}
* @param isSupportedVersion {boolean}
* @param slitherEnabled {boolean}
* @param categoryIndex {number[]}
* @param groupedModules {any}
* @param runner {any}
* @param _paq {any}
* @param message {any}
* @param showWarnings {boolean}
* @param allWarnings {React.RefObject<object>}
* @param warningContainer {React.RefObject<object>}
* @returns {Promise<void>}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function run (lastCompilationResult, lastCompilationSource, currentFile: string, state: RemixUiStaticAnalyserState, props: RemixUiStaticAnalyserProps, isSupportedVersion, showSlither, categoryIndex: number[], groupedModules, runner, _paq, message, showWarnings, allWarnings: React.RefObject<any>, warningContainer: React.RefObject<any>, calculateWarningStateEntries: (e:[string, any][]) => {length: number, errors: any[] }, warningState, setHints: React.Dispatch<React.SetStateAction<SolHintReport[]>>, hints: SolHintReport[], setSlitherWarnings: React.Dispatch<React.SetStateAction<any[]>>, setSsaWarnings: React.Dispatch<React.SetStateAction<any[]>>) {
if (!isSupportedVersion) return
if (state.data !== null) {
if (lastCompilationResult && (categoryIndex.length > 0 || showSlither)) {
const warningMessage = []
const warningErrors = []
// Run solhint
const hintsResult = await props.analysisModule.call('solhint', 'lint', state.file)
setHints(hintsResult)
const warningResult = calculateWarningStateEntries(Object.entries(warningState))
props.analysisModule.emit('statusChanged', { key: hints.length+warningResult.length,
title: `${hints.length+warningResult.length} warning${hints.length+warningResult.length === 1 ? '' : 's'}`, type: 'warning'})
// Remix Analysis
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyze', 'remixAnalyzer'])
const results = runner.run(lastCompilationResult, categoryIndex)
for (const result of results) {
let moduleName
Object.keys(groupedModules).map(key => {
groupedModules[key].forEach(el => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
// iterate over the warnings and create an object
for (const item of result.report) {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if (item.location) {
const split = item.location.split(':')
const file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.sources)[file]
}
if(fileName !== currentFile) {
const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName)
if (file.startsWith('.deps') || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true
}
const msg = message(result.name, item.warning, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
isLibrary,
errLine: row,
errCol: column,
item: item,
name: result.name,
locationString,
more: item.more,
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
setSsaWarnings(warningErrors)
}
}
// Slither Analysis
if (showSlither) {
try {
const compilerState = await props.analysisModule.call('solidity', 'getCompilerState')
const { currentVersion, optimize, evmVersion } = compilerState
await props.analysisModule.call('terminal', 'log', { type: 'log', value: '[Slither Analysis]: Running...' })
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyze', 'slitherAnalyzer'])
const result: SlitherAnalysisResults = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion })
if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'log', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data
for (const item of report) {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if (item.sourceMap && item.sourceMap.length) {
const path = item.sourceMap[0].source_mapping.filename_relative
let fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path)
if (fileIndex === -1) {
const getpath = await props.analysisModule.call('fileManager', 'getUrlFromPath', path)
fileIndex = Object.keys(lastCompilationResult.sources).indexOf(getpath.file)
}
if (fileIndex >= 0) {
location = {
start: item.sourceMap[0].source_mapping.start,
length: item.sourceMap[0].source_mapping.length
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
fileIndex,
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.sources)[fileIndex]
}
}
if(fileName !== currentFile) {
const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName)
if (file.startsWith('.deps') || (file.includes('.deps')) || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true
}
const msg = message(item.title, item.description, item.more ?? '', fileName, locationString)
const options = {
type: item.sourceMap[0].type,
useSpan: true,
errFile: fileName,
fileName,
isLibrary,
errLine: row,
errCol: column,
item: { warning: item.description },
name: item.title,
locationString,
more: item.more ?? '',
location: location
}
const slitherwarnings = []
setSlitherWarnings((prev) => {
slitherwarnings.push(...prev)
slitherwarnings.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
return slitherwarnings
})
}
showWarnings(warningMessage, 'warningModuleName')
}
} catch(error) {
props.analysisModule.call('terminal', 'log', { type: 'error', value: '[Slither Analysis]: Error occured! See remixd console for details.' })
showWarnings(warningMessage, 'warningModuleName')
}
} else showWarnings(warningMessage, 'warningModuleName')
} else {
if (categoryIndex.length) {
warningContainer.current.innerText = 'No compiled AST available'
}
props.event.trigger('staticAnaysisWarning', [-1])
}
}
}

@ -0,0 +1,44 @@
import React from 'react'
import { ErrorRendererOptions } from '../../staticanalyser'
type BasicTitleProps = {
warningStateEntries: any
hideWarnings?: boolean
}
type warningResultOption = {
hasWarning: boolean
msg: string
options: ErrorRendererOptions
}
type WarningResultType = {
categoryName: string
opts: warningResultOption[]
}
export function calculateWarningStateEntries(entries: [string, any][]) {
let warninglength = 0
entries.forEach((entry) => {
warninglength += entry[1].length
})
let errors = []
entries.forEach((entry) => {
errors = entry[1].filter(x => x.options.type === 'error')
})
return { length: warninglength, errors }
}
export function BasicTitle(props: BasicTitleProps) {
return (
<span>Remix{props.warningStateEntries.length > 0 && !props.hideWarnings ? <i data-id="StaticAnalysisErrorCount" className="badge badge-info rounded-circle ml-1">{calculateWarningStateEntries(props.warningStateEntries).length}</i>: (
<i className="badge badge-info rounded-circle ml-1">
{
calculateWarningStateEntries(props.warningStateEntries).errors.length
}
</i>
)}
</span>
)
}

@ -1,11 +1,16 @@
export const initialState = {
file: null,
import { RemixUiStaticAnalyserReducerActionType, RemixUiStaticAnalyserState } from "../../staticanalyser"
export const initialState: RemixUiStaticAnalyserState = {
file: '',
source: null,
languageVersion: null,
data: null
languageVersion: '',
data: null,
input: '',
version: ''
}
export const analysisReducer = (state, action) => {
export const analysisReducer = (state: RemixUiStaticAnalyserState,
action: RemixUiStaticAnalyserReducerActionType) => {
switch (action.type) {
case 'compilationFinished':
return {

@ -1,4 +1,4 @@
import React, { useEffect, useState, useReducer, useRef } from 'react' // eslint-disable-line
import React, { useEffect, useState, useReducer, useRef, Fragment } from 'react' // eslint-disable-line
import Button from './Button/StaticAnalyserButton' // eslint-disable-line
import { util } from '@remix-project/remix-lib'
import _ from 'lodash'
@ -9,7 +9,11 @@ import ErrorRenderer from './ErrorRenderer' // eslint-disable-line
import { compilation } from './actions/staticAnalysisActions'
import { initialState, analysisReducer } from './reducers/staticAnalysisReducer'
import { CodeAnalysis } from '@remix-project/remix-analyzer'
import { CustomTooltip } from '@remix-ui/helper'
import Tab from 'react-bootstrap/Tab'
import Tabs from 'react-bootstrap/Tabs'
import { AnalysisTab, SolHintReport } from '../staticanalyser'
import { run } from './actions/staticAnalysisActions'
import { BasicTitle, calculateWarningStateEntries } from './components/BasicTitle'
declare global {
interface Window {
@ -22,7 +26,7 @@ const _paq = window._paq = window._paq || [] //eslint-disable-line
export interface RemixUiStaticAnalyserProps {
registry: any,
event: any,
analysisModule: any
analysisModule: AnalysisTab
}
export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
@ -62,19 +66,28 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
return indexOfCategory
}
const [autoRun, setAutoRun] = useState(true)
const [slitherEnabled, setSlitherEnabled] = useState(false)
const [basicEnabled, setBasicEnabled] = useState(true)
const [solhintEnabled, setSolhintEnabled] = useState(true) // assuming that solhint is always enabled
const [showSlither, setShowSlither] = useState(false)
const [isSupportedVersion, setIsSupportedVersion] = useState(false)
let [showLibsWarning, setShowLibsWarning] = useState(false) // eslint-disable-line prefer-const
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
const [warningState, setWarningState] = useState({})
const [runButtonTitle, setRunButtonTitle] = useState<string>('Run Static Analysis')
const [hideWarnings, setHideWarnings] = useState(false)
const [hints, setHints] = useState<SolHintReport[]>([])
const [slitherWarnings, setSlitherWarnings] = useState([])
const [ssaWarnings, setSsaWarnings] = useState([])
const warningContainer = useRef(null)
const allWarnings = useRef({})
const [state, dispatch] = useReducer(analysisReducer, initialState)
const [runButtonTitle, setRunButtonTitle] = useState<string>(`Run analysis`)
/**
* Disable static analysis for contracts whose compiler version is
* less than 0.4.12
* @param version {string} - Solidity compiler version
*/
const setDisableForRun = (version: string) => {
const truncateVersion = (version: string) => {
const tmp: RegExpExecArray | null = /^(\d+.\d+.\d+)/.exec(version)
@ -85,7 +98,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setRunButtonTitle('Select Solidity compiler version greater than 0.4.12.')
} else {
setIsSupportedVersion(true)
setRunButtonTitle('Run static analysis')
setRunButtonTitle(`${state && state.data && state.file.length > 0 ? 'Run analysis': 'To run analysis tools, first compile a contract.'}`)
}
}
@ -96,15 +109,16 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect(() => {
setWarningState({})
const runAnalysis = async () => {
await run(state.data, state.source, state.file)
}
if (autoRun) {
if (state.data !== null) {
runAnalysis().catch(console.error);
}
} else {
props.event.trigger('staticAnaysisWarning', [])
await run(state.data, state.source, state.file, state, props, isSupportedVersion, showSlither, categoryIndex, groupedModules, runner,_paq, message, showWarnings, allWarnings, warningContainer,calculateWarningStateEntries, warningState, setHints, hints, setSlitherWarnings, setSsaWarnings)
}
props.event.trigger('staticAnaysisWarning', [])
// if (basicEnabled) {
// if (state.data !== null) {
// runAnalysis().catch(console.error);
// }
// } else {
// props.event.trigger('staticAnaysisWarning', [])
// }
return () => { }
}, [state])
@ -119,12 +133,14 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
// Reset badge
props.event.trigger('staticAnaysisWarning', [])
// Reset state
dispatch({ type: '', payload: {} })
dispatch({ type: '', payload: initialState })
setHints([])
setSlitherWarnings([])
setSsaWarnings([])
// Show 'Enable Slither Analysis' checkbox
if (currentWorkspace && currentWorkspace.isLocalhost === true) setShowSlither(true)
else {
setShowSlither(false)
setSlitherEnabled(false)
}
})
props.analysisModule.on('manager', 'pluginDeactivated', (plugin) => {
@ -132,12 +148,14 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
if (plugin.name === 'remixd') {
// Reset warning state
setWarningState([])
setHints([])
setSlitherWarnings([])
setSsaWarnings([])
// Reset badge
props.event.trigger('staticAnaysisWarning', [])
// Reset state
dispatch({ type: '', payload: {} })
dispatch({ type: '', payload: initialState })
setShowSlither(false)
setSlitherEnabled(false)
}
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -147,7 +165,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
return () => { }
}, [props])
const message = (name, warning, more, fileName, locationString) : string => {
const message = (name: string, warning: any, more?: string, fileName?: string, locationString?: string) : string => {
return (`
<span className='d-flex flex-column'>
<span className='h6 font-weight-bold'>${name}</span>
@ -207,154 +225,6 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
filterWarnings()
}
const run = async (lastCompilationResult, lastCompilationSource, currentFile) => {
if (!isSupportedVersion) return
if (state.data !== null) {
if (lastCompilationResult && (categoryIndex.length > 0 || slitherEnabled)) {
const warningMessage = []
const warningErrors = []
// Remix Analysis
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyze', 'remixAnalyzer'])
const results = runner.run(lastCompilationResult, categoryIndex)
for (const result of results) {
let moduleName
Object.keys(groupedModules).map(key => {
groupedModules[key].forEach(el => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
for (const item of result.report) {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if (item.location) {
const split = item.location.split(':')
const file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.sources)[file]
}
if(fileName !== currentFile) {
const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName)
if (file.startsWith('.deps') || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true
}
const msg = message(result.name, item.warning, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
isLibrary,
errLine: row,
errCol: column,
item: item,
name: result.name,
locationString,
more: item.more,
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
}
}
// Slither Analysis
if (slitherEnabled) {
try {
const compilerState = await props.analysisModule.call('solidity', 'getCompilerState')
const { currentVersion, optimize, evmVersion } = compilerState
await props.analysisModule.call('terminal', 'log', { type: 'log', value: '[Slither Analysis]: Running...' })
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyze', 'slitherAnalyzer'])
const result = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion })
if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'log', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data
for (const item of report) {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if (item.sourceMap && item.sourceMap.length) {
let path = item.sourceMap[0].source_mapping.filename_relative
let fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path)
if (fileIndex === -1) {
path = await props.analysisModule.call('fileManager', 'getUrlFromPath', path)
fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path.file)
}
if (fileIndex >= 0) {
location = {
start: item.sourceMap[0].source_mapping.start,
length: item.sourceMap[0].source_mapping.length
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
fileIndex,
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.sources)[fileIndex]
}
}
if(fileName !== currentFile) {
const {file, provider} = await props.analysisModule.call('fileManager', 'getPathFromUrl', fileName)
if (file.startsWith('.deps') || (provider.type === 'localhost' && file.startsWith('localhost/node_modules'))) isLibrary = true
}
const msg = message(item.title, item.description, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
isLibrary,
errLine: row,
errCol: column,
item: { warning: item.description },
name: item.title,
locationString,
more: item.more,
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
}
showWarnings(warningMessage, 'warningModuleName')
}
} catch(error) {
props.analysisModule.call('terminal', 'log', { type: 'error', value: '[Slither Analysis]: Error occured! See remixd console for details.' })
showWarnings(warningMessage, 'warningModuleName')
}
} else showWarnings(warningMessage, 'warningModuleName')
} else {
if (categoryIndex.length) {
warningContainer.current.innerText = 'No compiled AST available'
}
props.event.trigger('staticAnaysisWarning', [-1])
}
}
}
const handleCheckAllModules = (groupedModules) => {
const index = groupedModuleIndex(groupedModules)
@ -382,19 +252,29 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
}
const handleSlitherEnabled = () => {
if (slitherEnabled) {
setSlitherEnabled(false)
const handleSlitherEnabled = async () => {
const checkRemixd = await props.analysisModule.call('manager', 'isActive', 'remixd')
if (showSlither) {
setShowSlither(false)
}
if(!showSlither) {
setShowSlither(true)
}
}
const handleBasicEnabled = () => {
if (basicEnabled) {
setBasicEnabled(false)
} else {
setSlitherEnabled(true)
setBasicEnabled(true)
}
}
const handleAutoRun = () => {
if (autoRun) {
setAutoRun(false)
const handleLinterEnabled = () => {
if (solhintEnabled) {
setSolhintEnabled(false)
} else {
setAutoRun(true)
setSolhintEnabled(true)
}
}
@ -474,115 +354,379 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
)
}
const handleHideWarnings = () => {
setHideWarnings(!hideWarnings)
}
const hintErrors = hints.filter(hint => hint.type === 'error')
const slitherErrors = slitherWarnings.filter(slitherError => slitherError.options.type === 'error')
const tabKeys = [
{
tabKey: "linter",
child: (
<>
{hints.length > 0 ? (
<div id="solhintlintingresult" className="mb-5">
<div className="mb-4 pt-2">
<Fragment>
{!hideWarnings
? hints.map((hint, index) => (
<div
key={index}
className={`${
hint.type === "warning"
? "alert alert-warning"
: "alert alert-danger"
}`}
style={{ cursor: "pointer" }}
>
<div
onClick={async () => {
await props.analysisModule.call(
"editor",
"discardHighlight"
);
await props.analysisModule.call(
"editor",
"highlight",
{
end: {
line: hint.line,
column: hint.column + 1,
},
start: {
line: hint.line,
column: hint.column,
},
},
state.file,
"",
{ focus: true }
);
}}
>
<span className="text-wrap">
{hint.formattedMessage}
</span>
<span>{hint.type}</span>
<br />
<span>{`${hint.column}:${hint.line}`}</span>
</div>
</div>
))
: hintErrors.map((hint, index) => (
<div
key={index}
className="alert alert-danger"
style={{ cursor: "pointer" }}
>
<div
onClick={async () => {
await props.analysisModule.call(
"editor",
"discardHighlight"
);
await props.analysisModule.call(
"editor",
"highlight",
{
end: {
line: hint.line,
column: hint.column + 1,
},
start: {
line: hint.line,
column: hint.column,
},
},
state.file,
"",
{ focus: true }
);
}}
>
<span className="text-wrap">
{hint.formattedMessage}
</span>
<span>{hint.type}</span>
<br />
<span>{`${hint.column}:${hint.line}`}</span>
</div>
</div>
))}
</Fragment>
</div>
</div>
) : <span className="display-6">No Results.</span>}
</>
),
title: (
<span>
Linter
{hints.length > 0 ? (
hideWarnings ? (
<i className="badge badge-info rounded-circle ml-1">
{hintErrors.length}
</i>
) : (
<i className="badge badge-info rounded-circle ml-1">
{hints.length}
</i>
)
) : null}
</span>
),
},
{
tabKey: "basic",
title: (
<BasicTitle
warningStateEntries={Object.entries(warningState)}
hideWarnings={hideWarnings}
/>
),
child: (
<>
{Object.entries(warningState).length > 0 ? (
<div id="staticanalysisresult">
<div className="mb-4 pt-2">
{Object.entries(warningState).map((element, index) => (
<div key={index}>
{element[1]["map"](
(x,i) => // eslint-disable-line dot-notation
x.hasWarning && !hideWarnings
? ( // eslint-disable-next-line dot-notation
<div
data-id={`staticAnalysisModule${x.warningModuleName}${i}`}
id={`staticAnalysisModule${x.warningModuleName}${i}`}
key={i}
>
<ErrorRenderer
name={`staticAnalysisModule${x.warningModuleName}${i}`}
message={x.msg}
opt={x.options}
warningErrors={x.warningErrors}
editor={props.analysisModule}
/>
</div>
) : null
)}
{}
</div>
))}
</div>
</div>
) : <span className="display-6">No Results.</span>}
</>
),
},
{
tabKey: "slither",
title: (
<span>
Slither
{slitherWarnings.length > 0 ? (
hideWarnings ? (
<i className="badge badge-info rounded-circle ml-1">
{slitherErrors.length}
</i>
) : (
<i className="badge badge-info rounded-circle ml-1">
{slitherWarnings.length}
</i>
)
) : null}
</span>
),
child: (
<>
{slitherWarnings.length > 0 ? (
<div id="solhintlintingresult" className="mb-5">
<div className="mb-4 pt-2">
<Fragment>
{!hideWarnings
? showLibsWarning ? slitherWarnings.filter(warning => warning.isLibrary).map((warning, index) => (
<div
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`}
key={index}
>
<ErrorRenderer
name={`staticAnalysisModule${warning.warningModuleName}${index}`}
message={warning.msg}
opt={warning.options}
warningErrors={warning.warningErrors}
editor={props.analysisModule}
/>
</div>
)) : slitherWarnings.map((warning, index) => (
<div
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`}
key={index}
>
<ErrorRenderer
name={`staticAnalysisModule${warning.warningModuleName}${index}`}
message={warning.msg}
opt={warning.options}
warningErrors={warning.warningErrors}
editor={props.analysisModule}
/>
</div>
))
: slitherWarnings.filter(x => x.options.type === 'error').map((warning, index) => (
<div
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`}
key={index}
>
<ErrorRenderer
name={`staticAnalysisModule${warning.warningModuleName}${index}`}
message={warning.msg}
opt={warning.options}
warningErrors={warning.warningErrors}
editor={props.analysisModule}
/>
</div>
))}
</Fragment>
</div>
</div>
) : <span className="display-6">No Result</span>}
</>
),
},
];
const checkBasicStatus = () => {
return Object.values(groupedModules).map((value: any) => {
return (value.map(x => {
return x._index.toString()
}))
}).flat().every(el => categoryIndex.includes(el))
}
return (
<div className="analysis_3ECCBV px-3 pb-1">
<div className="my-2 d-flex flex-column align-items-left">
<div className="d-flex justify-content-between" id="staticanalysisButton">
<RemixUiCheckbox
<div className="d-flex flex-column mb-3" id="staticanalysisButton">
<div className="mb-3 d-flex justify-content-start">
<RemixUiCheckbox
id="checkAllEntries"
inputType="checkbox"
title="Select all Remix analysis modules"
title="Remix analysis is a basic analysis tool for Remix Ide."
checked={Object.values(groupedModules).map((value: any) => {
return (value.map(x => {
return x._index.toString()
}))
}).flat().every(el => categoryIndex.includes(el))}
label="Select all"
onClick={() => handleCheckAllModules(groupedModules)}
label="Remix"
onClick={() => {
handleCheckAllModules(groupedModules)
}}
onChange={() => {}}
tooltipPlacement={'top-start'}
tooltipPlacement={'bottom-start'}
optionalClassName="mr-3"
/>
<RemixUiCheckbox
id="autorunstaticanalysis"
id="solhintstaticanalysis"
inputType="checkbox"
title="Run static analysis after the compilation"
onClick={handleAutoRun}
checked={autoRun}
label="Autorun"
title="Run solhint static analysis."
onClick={handleLinterEnabled}
checked={solhintEnabled}
label="Linter"
onChange={() => {}}
tooltipPlacement={'bottom-start'}
/>
<Button
buttonText="Run"
title={runButtonTitle}
onClick={async () => await run(state.data, state.source, state.file)}
disabled={(state.data === null || categoryIndex.length === 0) && !slitherEnabled || !isSupportedVersion }
tooltipPlacement={'top-start'}
optionalClassName="mr-3"
/>
</div>
{ showSlither &&
<div className="d-flex mt-2" id="enableSlitherAnalysis">
<RemixUiCheckbox
id="enableSlither"
inputType="checkbox"
onClick={handleSlitherEnabled}
checked={slitherEnabled}
label="Enable Slither Analysis"
onChange={() => {}}
/>
<a className="mt-1 text-nowrap" href='https://remix-ide.readthedocs.io/en/latest/slither.html#enable-slither-analysis' target={'_blank'}>
<CustomTooltip
placement={'right'}
tooltipClasses="text-nowrap"
tooltipId="overlay-tooltip"
tooltipText={<span className="border bg-light text-dark p-1 pr-3" style={{minWidth: '230px' }}>Learn how to use Slither Analysis</span>}
>
<i style={{ fontSize: 'medium' }} className={'fal fa-info-circle ml-3'} aria-hidden="true"></i>
</CustomTooltip>
</a>
</div>
}
</div>
<div id="staticanalysismodules" className="list-group list-group-flush">
{Object.keys(groupedModules).map((categoryId, i) => {
const category = groupedModules[categoryId]
return (
categorySection(category, categoryId, i)
)
})
}
</div>
<div className="mt-2 p-2 d-flex border-top flex-column">
<span>Last results for:</span>
<span
className="text-break break-word word-break font-weight-bold"
id="staticAnalysisCurrentFile"
>
{state.file}
</span>
</div>
{Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
<RemixUiCheckbox
id="showLibWarnings"
name="showLibWarnings"
categoryId="showLibWarnings"
title="when checked, the results are also displayed for external contract libraries"
id="enableSlither"
inputType="checkbox"
checked={showLibsWarning}
label="Show warnings for external libraries"
onClick={handleShowLibsWarning}
onClick={handleSlitherEnabled}
checked={showSlither}
disabled={true}
label="Slither"
onChange={() => {}}
optionalClassName="mr-3"
title="To run Slither analysis, Remix IDE must be connected to your local filesystem with remixd."
/>
<br/>
<div className="mb-4">
{
(Object.entries(warningState).map((element, index) => (
<div key={index}>
{element[1]['length'] > 0 ? <span className="text-dark h6">{element[0]}</span> : null}
{element[1]['map']((x, i) => ( // eslint-disable-line dot-notation
x.hasWarning ? ( // eslint-disable-next-line dot-notation
<div data-id={`staticAnalysisModule${x.warningModuleName}${i}`} id={`staticAnalysisModule${x.warningModuleName}${i}`} key={i}>
<ErrorRenderer name={`staticAnalysisModule${x.warningModuleName}${i}`} message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/>
</div>
) : null
))}
</div>
)))
}
</div>
</div>
}
<Button
buttonText={`Analyse ${state.file}`}
title={`${runButtonTitle}`}
classList="btn btn-sm btn-primary btn-block"
onClick={async () => await run(state.data, state.source, state.file, state , props, isSupportedVersion, showSlither, categoryIndex, groupedModules, runner,_paq,
message, showWarnings, allWarnings, warningContainer, calculateWarningStateEntries, warningState, setHints, hints, setSlitherWarnings, setSsaWarnings)}
disabled={(state.data === null || !isSupportedVersion) || (!solhintEnabled && !basicEnabled) }
/>
{state && state.data !== null && state.source !== null && state.file.length > 0 ? (<div className="d-flex border-top flex-column">
<div className="mt-4 p-2 d-flex border-top flex-column">
<span>Last results for:</span>
<span
className="text-break break-word word-break font-weight-bold"
id="staticAnalysisCurrentFile"
>
{state.file}
</span>
</div>
<div className="border-top mt-3 pt-2 mb-2" id="staticanalysisresult">
<RemixUiCheckbox
id="showLibWarnings"
name="showLibWarnings"
categoryId="showLibWarnings"
title="When checked, the results are also displayed for external contract libraries."
inputType="checkbox"
checked={showLibsWarning}
label="Show warnings for external libraries"
onClick={handleShowLibsWarning}
onChange={() => {}}
tooltipPlacement="top-start"
/>
<RemixUiCheckbox
id="hideWarnings"
name="hideWarnings"
title="When checked, general warnings from analysis are hidden."
inputType="checkbox"
checked={hideWarnings}
label="Hide warnings"
onClick={handleHideWarnings}
onChange={() => {}}
/>
</div>
<Tabs defaultActiveKey={tabKeys[0].tabKey}>
{
checkBasicStatus() ? <Tab
key={tabKeys[1].tabKey}
title={tabKeys[1].title}
eventKey={tabKeys[1].tabKey}
tabClassName="text-decoration-none font-weight-bold"
>
{tabKeys[1].child}
</Tab> : null
}
{solhintEnabled ? <Tab
key={tabKeys[0].tabKey}
title={tabKeys[0].title}
eventKey={tabKeys[0].tabKey}
tabClassName="text-decoration-none font-weight-bold"
>
{tabKeys[0].child}
</Tab> : null}
{ showSlither ? <Tab
key={tabKeys[2].tabKey}
title={tabKeys[2].title}
eventKey={tabKeys[2].tabKey}
tabClassName="text-decoration-none font-weight-bold"
>
{tabKeys[2].child}
</Tab> : null }
</Tabs>
</div>) : null}
</div>
</div>
</div>
)
}

@ -0,0 +1,138 @@
import { CompilationResult, SourceWithTarget } from '@remixproject/plugin-api'
import { ViewPlugin } from '@remixproject/engine-web';
import { EventEmitter } from 'events';
import Registry from '../state/registry';
export declare class AnalysisTab extends ViewPlugin {
event: EventManager;
events: EventEmitter;
registry: Registry;
element: HTMLDivElement;
_components: any;
_deps: {
offsetToLineColumnConverter: any;
};
dispatch: any;
constructor();
onActivation(): Promise<void>;
setDispatch(dispatch: any): void;
render(): JSX.Element;
updateComponent(state: any): JSX.Element;
renderComponent(): void;
}
type RemixUiStaticAnalyserState = {
file: string,
source: SourceWithTarget,
languageVersion: string,
data: CompilationResult
input?: string
version?: string
}
type SolHintReport = {
column: number,
line: number,
type: 'warning' | 'error',
formattedMessage: string,
options?: ErrorRendererOptions
}
type SolHintTabChildProps = {
analysisModule: AnalysisTab
currentFile: string
hints: SolHintReport[]
}
type RemixUiStaticAnalyserReducerActionType = {
type: 'compilationFinished' | '' | any,
payload: RemixUiStaticAnalyserState
}
interface ErrorRendererProps {
message: any;
opt: ErrorRendererOptions,
warningErrors: any
editor: any,
name: string,
}
type ErrorRendererOptions = {
"type": "warning" | "error",
"useSpan": true,
"errFile": string
"fileName": string,
"isLibrary": boolean,
"errLine": number,
"errCol": number,
"item": {
"warning": string,
"location": string
},
"name": string,
"locationString": string,
"location": {
"start": {
"line": number,
"column": number
},
"end": {
"line": number,
"column": number
}
}
}
type SlitherAnalysisResultType = {
description: string,
title: string,
confidence: string,
severity: string,
more?: any,
sourceMap: [
{
type: string,
name: string,
source_mapping: {
start: number,
length: number,
filename_relative: string,
filename_absolute: string,
filename_short: string,
is_dependency: false,
lines: number[],
starting_column: number,
ending_column: number
},
type_specific_fields: {
parent: {
type: string,
name: string,
source_mapping: {
start: number,
length: number,
filename_relative: string,
filename_absolute: string,
filename_short: string,
is_dependency: false,
lines: number[],
starting_column: number,
ending_column: number
}
},
signature: string
},
additional_fields: {
target: string,
convention: string
}
}
]
}
export type SlitherAnalysisResults = {
count: number,
data: SlitherAnalysisResultType[]
status: boolean
}

@ -52,13 +52,13 @@ function Badge ({ badgeStatus }: BadgeProps) {
placement={'right'}
tooltipClasses="text-nowrap"
tooltipId="verticalItemsbadge"
tooltipText={badgeStatus.title}
tooltipText={badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? 'There are multiple warnings or errors that might need to be fixed.' :badgeStatus.title}
>
<i
className={resolveClasses(badgeStatus.key, badgeStatus.type!)}
className={`${resolveClasses(badgeStatus.key, badgeStatus.type!)}`}
aria-hidden="true"
>
{badgeStatus.text}
{ badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? <span>&nbsp;</span> : badgeStatus.text }
</i>
</CustomTooltip>
) : null

@ -1,10 +1,11 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect } from 'react'
import { Plugin } from '@remixproject/engine'
import { IconRecord } from '../types'
import Icon from './Icon'
interface OtherIconsProps {
verticalIconsPlugin: any
verticalIconsPlugin: Plugin<any, any>
itemContextAction: (e: any, name: string, documentation: string) => Promise<void>
icons: IconRecord[]
theme: string

Loading…
Cancel
Save