Merge pull request #3779 from ethereum/solhint-ssa-fix

Static Analysis Checkbox Fix
pull/3796/head
yann300 1 year ago committed by GitHub
commit f66c256cdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      apps/remix-ide/src/remixAppManager.js
  2. 3
      apps/solhint/src/app/SolhintPluginClient.ts
  3. 4
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  4. 4
      libs/remix-ui/solidity-uml-gen/src/lib/components/UmlDownload.tsx
  5. 34
      libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
  6. 4
      libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
  7. 23
      libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
  8. 6
      libs/remix-ui/static-analyser/src/lib/components/BasicTitle.tsx
  9. 261
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  10. 15
      libs/remix-ui/vertical-icons-panel/src/lib/components/Badge.tsx
  11. 2
      libs/remix-ui/workspace/src/lib/components/file-explorer-context-menu.tsx

@ -228,16 +228,6 @@ export class RemixAppManager extends PluginManager {
sticky: true, sticky: true,
group: 7 group: 7
}) })
await this.call('filePanel', 'registerContextMenuItem', {
id: 'solhint',
name: 'lintContractCustomAction',
label: 'Lint Contract',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true
})
} }
} }

@ -65,7 +65,6 @@ export class SolHint extends PluginClient {
async lintContract(file: string) { async lintContract(file: string) {
const hints = await this.lint(file) const hints = await this.lint(file)
console.log({ hints })
this.emit('lintingFinished', hints) this.emit('lintingFinished', hints)
} }
@ -92,6 +91,8 @@ export class SolHint extends PluginClient {
line: report.line - 1 line: report.line - 1
} }
}) })
this.emit('lintingFinished', hints)
return hints return hints
} }

@ -37,7 +37,7 @@ export const RemixUiCheckbox = ({
visibility, visibility,
optionalClassName = '', optionalClassName = '',
display = 'flex', display = 'flex',
disabled = false, disabled,
tooltipPlacement = 'right', tooltipPlacement = 'right',
}: RemixUiCheckboxProps) => { }: RemixUiCheckboxProps) => {
@ -58,7 +58,7 @@ export const RemixUiCheckbox = ({
checked={checked} checked={checked}
disabled={disabled} disabled={disabled}
/> />
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }}> <label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }} aria-disabled={disabled}>
{name ? <div className="font-weight-bold">{itemName}</div> : ''} {name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label} {label}
</label> </label>

@ -86,7 +86,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
_paq.push([ _paq.push([
"trackEvent", "trackEvent",
"solidityumlgen", "solidityumlgen",
"download", "umlpngdownload",
"downloadAsPng", "downloadAsPng",
]); ]);
props.download("png") props.download("png")
@ -115,7 +115,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
_paq.push([ _paq.push([
"trackEvent", "trackEvent",
"solUmlgen", "solUmlgen",
"download", "umlpdfdownload",
"downloadAsPdf", "downloadAsPdf",
]); ]);
props.download("pdf") props.download("pdf")

@ -19,20 +19,32 @@ const StaticAnalyserButton = ({
const defaultStyle = "btn btn-sm w-25 btn-primary" const defaultStyle = "btn btn-sm w-25 btn-primary"
const newclassList = disabled && classList.length > 0 ? `${classList} disabled` : const newclassList = disabled && classList.length > 0 ? `${classList} disabled` :
classList.length === 0 && disabled ? `${defaultStyle} disabled` : classList.length > 0 ? `${classList}` : defaultStyle classList.length === 0 && disabled ? `${defaultStyle} disabled` : classList.length > 0 ? `${classList}` : defaultStyle
return (
const buttonWithoutTooltip = () => (
<button id="staticAnalysisRunBtn" className={newclassList} disabled={disabled} onClick={onClick}> <button id="staticAnalysisRunBtn" className={newclassList} disabled={disabled} onClick={onClick}>
<CustomTooltip <span className="pl-3 pr-4">
placement="right" {buttonText}
tooltipId="ssaRunButtonTooltip" </span>
tooltipClasses="text-nowrap"
tooltipText={title}
>
<span className="pl-3 pr-4">
{buttonText}
</span>
</CustomTooltip>
</button> </button>
) )
const buttonWithTooltip = () => (
<button id="staticAnalysisRunBtn" className={newclassList} disabled={disabled} onClick={onClick}>
<CustomTooltip
placement="right"
tooltipId="ssaRunButtonTooltip"
tooltipClasses="text-nowrap"
tooltipText={title}
>
<span className="pl-3 pr-4">
{buttonText}
</span>
</CustomTooltip>
</button>)
return (
title && title.length > 0 ? buttonWithTooltip() : buttonWithoutTooltip()
)
} }
export default StaticAnalyserButton export default StaticAnalyserButton

@ -47,9 +47,9 @@ const ErrorRenderer = ({ message, opt, editor, name }: ErrorRendererProps) => {
return ( return (
<div> <div>
<div className={`sol ${opt.type} ${classList}`}> <div className={`sol ${opt.type} ${classList}`}>
<span className='d-flex flex-column' data-id={`${name}Button`} onClick={async () => await handlePointToErrorOnClick(opt.location, opt.fileName)}> <span className='d-flex flex-column' data-id={`${name}Button`} onClick={async () => await handlePointToErrorOnClick(opt.location, opt.fileName)} style={{ cursor: "pointer", overflow: 'hidden', textOverflow: 'ellipsis' }}>
<span className='h6 font-weight-bold'>{opt.name}</span> <span className='h6 font-weight-bold'>{opt.name}</span>
{ opt.item.warning } <span>{ opt.item.warning }</span>
{opt.item.more {opt.item.more
? <span><a href={opt.item.more} target='_blank'>more</a></span> ? <span><a href={opt.item.more} target='_blank'>more</a></span>
: <span> </span> : <span> </span>

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { CompilationResult, SourceWithTarget } from '@remixproject/plugin-api' import { CompilationResult, SourceWithTarget } from '@remixproject/plugin-api'
import React from 'react' //eslint-disable-line import React from 'react' //eslint-disable-line
import { AnalysisTab, RemixUiStaticAnalyserReducerActionType, RemixUiStaticAnalyserState, SolHintReport, SlitherAnalysisResults } from '../../staticanalyser' import { AnalysisTab, RemixUiStaticAnalyserReducerActionType, RemixUiStaticAnalyserState, SolHintReport, SlitherAnalysisResults } from '../../staticanalyser'
@ -42,8 +43,12 @@ export const compilation = (analysisModule: AnalysisTab,
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars // 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[]>>) { 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[]>>,
slitherEnabled: boolean, setStartAnalysis: React.Dispatch<React.SetStateAction<boolean>>) {
setStartAnalysis(true)
setHints([])
setSsaWarnings([])
setSlitherWarnings([])
if (!isSupportedVersion) return if (!isSupportedVersion) return
if (state.data !== null) { if (state.data !== null) {
if (lastCompilationResult && (categoryIndex.length > 0 || showSlither)) { if (lastCompilationResult && (categoryIndex.length > 0 || showSlither)) {
@ -117,11 +122,12 @@ export async function run (lastCompilationResult, lastCompilationSource, current
} }
warningErrors.push(options) warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName }) warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
setSsaWarnings(warningErrors) setSsaWarnings(warningMessage)
} }
} }
// Slither Analysis // Slither Analysis
if (showSlither) { if (showSlither && slitherEnabled) {
setSlitherWarnings([])
try { try {
const compilerState = await props.analysisModule.call('solidity', 'getCompilerState') const compilerState = await props.analysisModule.call('solidity', 'getCompilerState')
const { currentVersion, optimize, evmVersion } = compilerState const { currentVersion, optimize, evmVersion } = compilerState
@ -169,7 +175,7 @@ export async function run (lastCompilationResult, lastCompilationSource, current
} }
const msg = message(item.title, item.description, item.more ?? '', fileName, locationString) const msg = message(item.title, item.description, item.more ?? '', fileName, locationString)
const options = { const options = {
type: item.sourceMap[0].type, type: item.sourceMap && item.sourceMap.length > 0 ? item.sourceMap[0].type : '',
useSpan: true, useSpan: true,
errFile: fileName, errFile: fileName,
fileName, fileName,
@ -183,11 +189,9 @@ export async function run (lastCompilationResult, lastCompilationSource, current
location: location location: location
} }
const slitherwarnings = []
setSlitherWarnings((prev) => { setSlitherWarnings((prev) => {
slitherwarnings.push(...prev) prev.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
slitherwarnings.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' }) return prev
return slitherwarnings
}) })
} }
showWarnings(warningMessage, 'warningModuleName') showWarnings(warningMessage, 'warningModuleName')
@ -197,6 +201,7 @@ export async function run (lastCompilationResult, lastCompilationSource, current
showWarnings(warningMessage, 'warningModuleName') showWarnings(warningMessage, 'warningModuleName')
} }
} else showWarnings(warningMessage, 'warningModuleName') } else showWarnings(warningMessage, 'warningModuleName')
setStartAnalysis(false)
} else { } else {
if (categoryIndex.length) { if (categoryIndex.length) {
warningContainer.current.innerText = 'No compiled AST available' warningContainer.current.innerText = 'No compiled AST available'

@ -32,13 +32,13 @@ export function calculateWarningStateEntries(entries: [string, any][]) {
export function BasicTitle(props: BasicTitleProps) { export function BasicTitle(props: BasicTitleProps) {
return ( 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>: ( <span>Remix{props.warningStateEntries.length > 0 ? !props.hideWarnings ? <i data-id="StaticAnalysisErrorCount" className={`badge ${calculateWarningStateEntries(props.warningStateEntries).length > 0 ? 'badge-warning' : 'badge-danger'} rounded-circle ml-1 text-center`}>{calculateWarningStateEntries(props.warningStateEntries).length}</i>: (
<i className="badge badge-info rounded-circle ml-1"> <i className="badge badge-warning rounded-circle ml-1 text-center">
{ {
calculateWarningStateEntries(props.warningStateEntries).errors.length calculateWarningStateEntries(props.warningStateEntries).errors.length
} }
</i> </i>
)} ) : null}
</span> </span>
) )
} }

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState, useReducer, useRef, Fragment } 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 Button from './Button/StaticAnalyserButton' // eslint-disable-line
import { util } from '@remix-project/remix-lib' import { util } from '@remix-project/remix-lib'
@ -69,6 +71,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const [basicEnabled, setBasicEnabled] = useState(true) const [basicEnabled, setBasicEnabled] = useState(true)
const [solhintEnabled, setSolhintEnabled] = useState(true) // assuming that solhint is always enabled const [solhintEnabled, setSolhintEnabled] = useState(true) // assuming that solhint is always enabled
const [showSlither, setShowSlither] = useState(false) const [showSlither, setShowSlither] = useState(false)
const [slitherEnabled, setSlitherEnabled] = useState(false)
const [startAnalysis, setStartAnalysis] = useState(false)
const [isSupportedVersion, setIsSupportedVersion] = useState(false) const [isSupportedVersion, setIsSupportedVersion] = useState(false)
let [showLibsWarning, setShowLibsWarning] = useState(false) // eslint-disable-line prefer-const let [showLibsWarning, setShowLibsWarning] = useState(false) // eslint-disable-line prefer-const
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules)) const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
@ -109,16 +113,9 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect(() => { useEffect(() => {
setWarningState({}) setWarningState({})
const runAnalysis = async () => { const runAnalysis = 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) 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, slitherEnabled, setStartAnalysis)
} }
props.event.trigger('staticAnaysisWarning', []) props.event.trigger('staticAnaysisWarning', [])
// if (basicEnabled) {
// if (state.data !== null) {
// runAnalysis().catch(console.error);
// }
// } else {
// props.event.trigger('staticAnaysisWarning', [])
// }
return () => { } return () => { }
}, [state]) }, [state])
@ -126,6 +123,17 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
props.analysisModule.call('solidity', 'getCompilerState').then((compilerState) => setDisableForRun(compilerState.currentVersion)) props.analysisModule.call('solidity', 'getCompilerState').then((compilerState) => setDisableForRun(compilerState.currentVersion))
}, []) }, [])
useEffect(() => {
const checkRemixdActive = async () => {
const remixdActive = await props.analysisModule.call('manager', 'isActive', 'remixd')
if (remixdActive) {
setSlitherEnabled(true)
setShowSlither(true)
}
}
checkRemixdActive()
}, [props])
useEffect(() => { useEffect(() => {
props.analysisModule.on('filePanel', 'setWorkspace', (currentWorkspace) => { props.analysisModule.on('filePanel', 'setWorkspace', (currentWorkspace) => {
// Reset warning state // Reset warning state
@ -138,9 +146,13 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setSlitherWarnings([]) setSlitherWarnings([])
setSsaWarnings([]) setSsaWarnings([])
// Show 'Enable Slither Analysis' checkbox // Show 'Enable Slither Analysis' checkbox
if (currentWorkspace && currentWorkspace.isLocalhost === true) setShowSlither(true) if (currentWorkspace && currentWorkspace.isLocalhost === true) {
setShowSlither(true)
setSlitherEnabled(true)
}
else { else {
setShowSlither(false) setShowSlither(false)
setSlitherEnabled(false)
} }
}) })
props.analysisModule.on('manager', 'pluginDeactivated', (plugin) => { props.analysisModule.on('manager', 'pluginDeactivated', (plugin) => {
@ -150,6 +162,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setWarningState([]) setWarningState([])
setHints([]) setHints([])
setSlitherWarnings([]) setSlitherWarnings([])
setSlitherEnabled(false)
setSsaWarnings([]) setSsaWarnings([])
// Reset badge // Reset badge
props.event.trigger('staticAnaysisWarning', []) props.event.trigger('staticAnaysisWarning', [])
@ -203,6 +216,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setWarningState(newWarningState) setWarningState(newWarningState)
} }
useEffect(() => {
if(hints.length > 0) {
props.event.trigger('staticAnaysisWarning', [hints.length])
}
},[hints.length, state])
const showWarnings = (warningMessage, groupByKey) => { const showWarnings = (warningMessage, groupByKey) => {
const resultArray = [] const resultArray = []
warningMessage.map(x => { warningMessage.map(x => {
@ -359,7 +378,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
} }
const hintErrors = hints.filter(hint => hint.type === 'error') const hintErrors = hints.filter(hint => hint.type === 'error')
const slitherErrors = slitherWarnings.filter(slitherError => slitherError.options.type === 'error') const noLibSlitherWarnings = slitherWarnings.filter(w => !w.options.isLibrary)
const slitherErrors = noLibSlitherWarnings.filter(slitherError => slitherError.options.type === 'error')
const tabKeys = [ const tabKeys = [
{ {
@ -379,33 +399,32 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
? "alert alert-warning" ? "alert alert-warning"
: "alert alert-danger" : "alert alert-danger"
}`} }`}
style={{ cursor: "pointer" }} style={{ cursor: "pointer", overflow: 'hidden', textOverflow: 'ellipsis' }}
> onClick={async () => {
<div await props.analysisModule.call(
onClick={async () => { "editor",
await props.analysisModule.call( "discardHighlight"
"editor", )
"discardHighlight" await props.analysisModule.call(
); "editor",
await props.analysisModule.call( "highlight",
"editor", {
"highlight", end: {
{ line: hint.line,
end: { column: hint.column + 1,
line: hint.line,
column: hint.column + 1,
},
start: {
line: hint.line,
column: hint.column,
},
}, },
state.file, start: {
"", line: hint.line,
{ focus: true } column: hint.column,
); },
}} },
> state.file,
"",
{ focus: true }
);
}}
>
<div>
<span className="text-wrap"> <span className="text-wrap">
{hint.formattedMessage} {hint.formattedMessage}
</span> </span>
@ -419,36 +438,36 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<div <div
key={index} key={index}
className="alert alert-danger" className="alert alert-danger"
style={{ cursor: "pointer" }} style={{ cursor: "pointer", overflow: 'hidden', textOverflow: 'ellipsis' }}
> onClick={async () => {
<div await props.analysisModule.call(
onClick={async () => { "editor",
await props.analysisModule.call( "discardHighlight"
"editor", );
"discardHighlight" await props.analysisModule.call(
); "editor",
await props.analysisModule.call( "highlight",
"editor", {
"highlight", end: {
{ line: hint.line,
end: { column: hint.column + 1,
line: hint.line,
column: hint.column + 1,
},
start: {
line: hint.line,
column: hint.column,
},
}, },
state.file, start: {
"", line: hint.line,
{ focus: true } column: hint.column,
); },
}} },
> state.file,
<span className="text-wrap"> "",
{ focus: true }
);
}}
>
<div>
<span className="text-wrap" style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
{hint.formattedMessage} {hint.formattedMessage}
</span> </span>
<br />
<span>{hint.type}</span> <span>{hint.type}</span>
<br /> <br />
<span>{`${hint.column}:${hint.line}`}</span> <span>{`${hint.column}:${hint.line}`}</span>
@ -458,7 +477,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
</Fragment> </Fragment>
</div> </div>
</div> </div>
) : <span className="display-6">No Results.</span>} ) : state.data && state.file.length > 0 && state.source && startAnalysis && hints.length > 0 ? <span className="ml-4 spinner-grow-sm d-flex justify-content-center">Loading...</span> : <span className="display-6 text-center">Nothing to report</span>}
</> </>
), ),
title: ( title: (
@ -466,11 +485,13 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
Linter Linter
{hints.length > 0 ? ( {hints.length > 0 ? (
hideWarnings ? ( hideWarnings ? (
<i className="badge badge-info rounded-circle ml-1"> <i className={`badge ${hints.filter(x => x.type === 'error').length > 0
? `badge-danger` : 'badge-warning'} rounded-circle ml-1 text-center`}>
{hintErrors.length} {hintErrors.length}
</i> </i>
) : ( ) : (
<i className="badge badge-info rounded-circle ml-1"> <i className={`badge ${hints.filter(x => x.type === 'error').length > 0
? `badge-danger` : 'badge-warning'} rounded-circle ml-1 text-center`}>
{hints.length} {hints.length}
</i> </i>
) )
@ -493,9 +514,10 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<div className="mb-4 pt-2"> <div className="mb-4 pt-2">
{Object.entries(warningState).map((element, index) => ( {Object.entries(warningState).map((element, index) => (
<div key={index}> <div key={index}>
{element[1]["map"]( { !hideWarnings && element[1]['length'] > 0 ? <span className="text-dark h6">{element[0]}</span> : null}
{!hideWarnings ? element[1]["map"](
(x,i) => // eslint-disable-line dot-notation (x,i) => // eslint-disable-line dot-notation
x.hasWarning && !hideWarnings x.hasWarning
? ( // eslint-disable-next-line dot-notation ? ( // eslint-disable-next-line dot-notation
<div <div
data-id={`staticAnalysisModule${x.warningModuleName}${i}`} data-id={`staticAnalysisModule${x.warningModuleName}${i}`}
@ -511,13 +533,30 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
/> />
</div> </div>
) : null ) : 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> </div>
</div> </div>
) : <span className="display-6">No Results.</span>} ) : state.data && state.file.length > 0 && state.source && startAnalysis && Object.entries(warningState).length > 0 ? <span className="ml-4 spinner-grow-sm d-flex justify-content-center">Loading...</span> : <span className="display-6 text-center">Nothing to report</span>}
</> </>
), ),
}, },
@ -528,12 +567,16 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
Slither Slither
{slitherWarnings.length > 0 ? ( {slitherWarnings.length > 0 ? (
hideWarnings ? ( hideWarnings ? (
<i className="badge badge-info rounded-circle ml-1"> <i className="badge badge-warning rounded-circle ml-1">
{slitherErrors.length} {slitherErrors.length}
</i> </i>
) : ( ) : showLibsWarning === true && hideWarnings === false ? (
<i className="badge badge-info rounded-circle ml-1"> <i className={`badge ${slitherErrors.length > 0 ? `badge-danger` : 'badge-warning'} rounded-circle ml-1 text-center`}>
{slitherWarnings.length} {slitherWarnings.length}
</i>
) : (
<i className={`badge ${slitherErrors.length > 0 ? `badge-danger` : 'badge-warning'} rounded-circle ml-1 text-center`}>
{noLibSlitherWarnings.length}
</i> </i>
) )
) : null} ) : null}
@ -546,7 +589,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<div className="mb-4 pt-2"> <div className="mb-4 pt-2">
<Fragment> <Fragment>
{!hideWarnings {!hideWarnings
? showLibsWarning ? slitherWarnings.filter(warning => warning.isLibrary).map((warning, index) => ( ? showLibsWarning ? slitherWarnings.map((warning, index) => (
<div <div
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`} data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`} id={`staticAnalysisModule${warning.warningModuleName}${index}`}
@ -560,7 +603,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
editor={props.analysisModule} editor={props.analysisModule}
/> />
</div> </div>
)) : slitherWarnings.map((warning, index) => ( )) : noLibSlitherWarnings.map((warning, index) => (
<div <div
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`} data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`} id={`staticAnalysisModule${warning.warningModuleName}${index}`}
@ -580,7 +623,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`} data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`} id={`staticAnalysisModule${warning.warningModuleName}${index}`}
key={index} key={index}
> >
<ErrorRenderer <ErrorRenderer
name={`staticAnalysisModule${warning.warningModuleName}${index}`} name={`staticAnalysisModule${warning.warningModuleName}${index}`}
message={warning.msg} message={warning.msg}
@ -593,7 +636,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
</Fragment> </Fragment>
</div> </div>
</div> </div>
) : <span className="display-6">No Result</span>} ) : state.data && state.file.length > 0 && state.source && startAnalysis && slitherWarnings.length > 0 ? <span className="ml-4 spinner-grow-sm d-flex justify-content-center">Loading...</span> : <span className="display-6 text-center">Nothing to report</span>}
</> </>
), ),
}, },
@ -615,7 +658,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<RemixUiCheckbox <RemixUiCheckbox
id="checkAllEntries" id="checkAllEntries"
inputType="checkbox" inputType="checkbox"
title="Remix analysis is a basic analysis tool for Remix Ide." title="Remix analysis runs a basic analysis."
checked={Object.values(groupedModules).map((value: any) => { checked={Object.values(groupedModules).map((value: any) => {
return (value.map(x => { return (value.map(x => {
return x._index.toString() return x._index.toString()
@ -633,12 +676,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<RemixUiCheckbox <RemixUiCheckbox
id="solhintstaticanalysis" id="solhintstaticanalysis"
inputType="checkbox" inputType="checkbox"
title="Run solhint static analysis." title="Linter runs SolHint static analysis."
onClick={handleLinterEnabled} onClick={handleLinterEnabled}
checked={solhintEnabled} checked={solhintEnabled }
label="Linter" label="Linter"
onChange={() => {}} onChange={() => {}}
tooltipPlacement={'top-start'} tooltipPlacement={'bottom-start'}
optionalClassName="mr-3" optionalClassName="mr-3"
/> />
@ -646,38 +689,46 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
id="enableSlither" id="enableSlither"
inputType="checkbox" inputType="checkbox"
onClick={handleSlitherEnabled} onClick={handleSlitherEnabled}
checked={showSlither} checked={showSlither && slitherEnabled}
disabled={true} disabled={slitherEnabled === false}
tooltipPlacement="bottom-start"
label="Slither" label="Slither"
onChange={() => {}} onChange={() => {}}
optionalClassName="mr-3" optionalClassName="mr-3"
title="To run Slither analysis, Remix IDE must be connected to your local filesystem with remixd." title={slitherEnabled ? "Slither runs Slither static analysis." : "To run Slither analysis, Remix IDE must be connected to your local filesystem with Remixd."}
/> />
</div> </div>
<Button {state.data && state.file.length > 0 && state.source ? <Button
buttonText={`Analyse ${state.file}`} buttonText={`Analyse ${state.file}`}
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, slitherEnabled, setStartAnalysis)}
disabled={(state.data === null || !isSupportedVersion) || (!solhintEnabled && !basicEnabled) }
/> : <Button
buttonText={`Analyze ${state.file}`}
title={`${runButtonTitle}`} title={`${runButtonTitle}`}
classList="btn btn-sm btn-primary btn-block" 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, 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)} message, showWarnings, allWarnings, warningContainer, calculateWarningStateEntries, warningState, setHints, hints, setSlitherWarnings, setSsaWarnings, slitherEnabled, setStartAnalysis)}
disabled={(state.data === null || !isSupportedVersion) || (!solhintEnabled && !basicEnabled) } 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"> {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"> {slitherWarnings.length > 0 || hints.length > 0 || Object.entries(warningState).length > 0 ? (
<span>Last results for:</span> <div className={`mt-4 p-2 d-flex ${slitherWarnings.length > 0 || hints.length > 0 || Object.entries(warningState).length > 0 ? 'border-top' : ''} flex-column`}>
<span <span>Last results for:</span>
className="text-break break-word word-break font-weight-bold" <span
id="staticAnalysisCurrentFile" className="text-break break-word word-break font-weight-bold"
> id="staticAnalysisCurrentFile"
{state.file} >
</span> {state.file}
</span>
</div> </div>
<div className="border-top mt-3 pt-2 mb-2" id="staticanalysisresult"> ) : null}
<div className="border-top mt-2 pt-2 mb-2" id="staticanalysisresult">
<RemixUiCheckbox <RemixUiCheckbox
id="showLibWarnings" id="showLibWarnings"
name="showLibWarnings" name="showLibWarnings"
categoryId="showLibWarnings" categoryId="showLibWarnings"
title="When checked, the results are also displayed for external contract libraries."
inputType="checkbox" inputType="checkbox"
checked={showLibsWarning} checked={showLibsWarning}
label="Show warnings for external libraries" label="Show warnings for external libraries"
@ -688,7 +739,6 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<RemixUiCheckbox <RemixUiCheckbox
id="hideWarnings" id="hideWarnings"
name="hideWarnings" name="hideWarnings"
title="When checked, general warnings from analysis are hidden."
inputType="checkbox" inputType="checkbox"
checked={hideWarnings} checked={hideWarnings}
label="Hide warnings" label="Hide warnings"
@ -696,13 +746,16 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
onChange={() => {}} onChange={() => {}}
/> />
</div> </div>
<Tabs defaultActiveKey={tabKeys[0].tabKey}> <Tabs
defaultActiveKey={tabKeys[0].tabKey}
className="px-1"
>
{ {
checkBasicStatus() ? <Tab checkBasicStatus() ? <Tab
key={tabKeys[1].tabKey} key={tabKeys[1].tabKey}
title={tabKeys[1].title} title={tabKeys[1].title}
eventKey={tabKeys[1].tabKey} eventKey={tabKeys[1].tabKey}
tabClassName="text-decoration-none font-weight-bold" tabClassName="text-decoration-none font-weight-bold px-2"
> >
{tabKeys[1].child} {tabKeys[1].child}
</Tab> : null </Tab> : null
@ -711,15 +764,15 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
key={tabKeys[0].tabKey} key={tabKeys[0].tabKey}
title={tabKeys[0].title} title={tabKeys[0].title}
eventKey={tabKeys[0].tabKey} eventKey={tabKeys[0].tabKey}
tabClassName="text-decoration-none font-weight-bold" tabClassName="text-decoration-none font-weight-bold px-2"
> >
{tabKeys[0].child} {tabKeys[0].child}
</Tab> : null} </Tab> : null}
{ showSlither ? <Tab { showSlither && slitherEnabled ? <Tab
key={tabKeys[2].tabKey} key={tabKeys[2].tabKey}
title={tabKeys[2].title} title={tabKeys[2].title}
eventKey={tabKeys[2].tabKey} eventKey={tabKeys[2].tabKey}
tabClassName="text-decoration-none font-weight-bold" tabClassName="text-decoration-none font-weight-bold px-2"
> >
{tabKeys[2].child} {tabKeys[2].child}
</Tab> : null } </Tab> : null }

@ -1,4 +1,6 @@
import React from 'react' /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React, { useEffect } from 'react'
import { BadgeStatus } from './Icon' import { BadgeStatus } from './Icon'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl'
@ -48,19 +50,14 @@ function Badge ({ badgeStatus }: BadgeProps) {
<> <>
{ {
badgeStatus && checkStatusKeyValue(badgeStatus.key, badgeStatus.type) ? ( badgeStatus && checkStatusKeyValue(badgeStatus.key, badgeStatus.type) ? (
<CustomTooltip
placement={'right'}
tooltipClasses="text-nowrap"
tooltipId="verticalItemsbadge"
tooltipText={badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? 'There are multiple warnings or errors that might need to be fixed.' :badgeStatus.title}
>
<i <i
className={`${resolveClasses(badgeStatus.key, badgeStatus.type!)}`} className={`${resolveClasses(badgeStatus.key, badgeStatus.type!)}`}
aria-hidden="true" aria-hidden="true"
> >
{ badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? <span>&nbsp;</span> : badgeStatus.text } { badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? badgeStatus.type === 'warning' || badgeStatus.type === 'error' ? <span>
<i className="far fa-exclamation-triangle"></i></span>
: <span>&nbsp;</span> : badgeStatus.text }
</i> </i>
</CustomTooltip>
) : null ) : null
} }
</> </>

@ -175,7 +175,7 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
publishFolderToGist(path, type) publishFolderToGist(path, type)
break break
default: default:
_paq.push(['trackEvent', 'fileExplorer', 'customAction', `${item.id}/${item.name}`]) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', `${item.id}/${item.name}`])
emit && emit({ ...item, path: [path] } as customAction) emit && emit({ ...item, path: [path] } as customAction)
break break
} }

Loading…
Cancel
Save