diff --git a/apps/remix-ide/src/app/tabs/analysis-tab.js b/apps/remix-ide/src/app/tabs/analysis-tab.js index 85b0887c5c..53a20a5042 100644 --- a/apps/remix-ide/src/app/tabs/analysis-tab.js +++ b/apps/remix-ide/src/app/tabs/analysis-tab.js @@ -38,6 +38,9 @@ class AnalysisTab extends ViewPlugin { } this.dispatch = null this.hints = [] + this.basicEnabled = false + this.solhintEnabled = false + this.slitherEnabled = false } async onActivation () { @@ -49,16 +52,15 @@ class AnalysisTab extends ViewPlugin { this.event.register('staticAnaysisWarning', (count) => { let payloadType = '' - const error = this.hints.find(hint => hint.type === 'error') - const warning = this.hints.find(hints => hints.type === 'warning') - if (error) { + const error = this.hints?.find(hint => hint.type === 'error') + if (error && this.solhintEnabled) { payloadType = 'error' } else { payloadType = 'warning' } if (count > 0) { - this.emit('statusChanged', { key: count, title: payloadType === 'error' ? `You have some problem${count === 1 ? '' : 's'}` : 'You have some warnings', type: payloadType }) + this.emit('statusChanged', { key: count, title: payloadType === 'error' ? `You have ${count} problem${count === 1 ? '' : 's'}` : `You have ${count} warnings`, type: payloadType }) } else if (count === 0) { this.emit('statusChanged', { key: 'succeed', title: 'no warnings or errors', type: 'success' }) } else { diff --git a/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx b/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx index 5d008958ec..ffe09549bf 100644 --- a/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx +++ b/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx @@ -1,20 +1,22 @@ -import { CustomTooltip } from '@remix-ui/helper'; -import React from 'react' //eslint-disable-line +import { CustomTooltip } from "@remix-ui/helper" +import React from "react"; //eslint-disable-line +import { RemixUiStaticAnalyserState } from "../staticanalyser" interface ErrorRendererProps { message: any; - opt: any, - warningErrors: any - editor: any, - name: string, + opt: any; + warningErrors: any; + editor: any; + name: string; + ssaState: RemixUiStaticAnalyserState } -const ErrorRenderer = ({ message, opt, editor, name }: ErrorRendererProps) => { +const ErrorRenderer = ({ message, opt, editor, name, ssaState }: ErrorRendererProps) => { const getPositionDetails = (msg: any) => { - const result = { } as Record + const result = {} as Record // To handle some compiler warning without location like SPDX license warning etc - if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg } + if (!msg.includes(":")) return { errLine: -1, errCol: -1, errFile: msg } // extract line / column let position = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/) @@ -23,46 +25,70 @@ const ErrorRenderer = ({ message, opt, editor, name }: ErrorRendererProps) => { // extract file position = msg.match(/^(https:.*?|http:.*?|.*?):/) - result.errFile = position ? position[1] : '' - return result + result.errFile = position ? position[1] : "" + return result; } const handlePointToErrorOnClick = async (location, fileName) => { - await editor.call('editor', 'discardHighlight') - await editor.call('editor', 'highlight', location, fileName, '', { focus: true }) + await editor.call("editor", "discardHighlight") + await editor.call("editor", "highlight", location, fileName, "", { + focus: true, + }) } if (!message) return let position = getPositionDetails(message) - if (!position.errFile || (opt.errorType && opt.errorType === position.errFile)) { + if ( + !position.errFile || + (opt.errorType && opt.errorType === position.errFile) + ) { // Updated error reported includes '-->' before file details - const errorDetails = message.split('-->') + const errorDetails = message.split("-->") // errorDetails[1] will have file details if (errorDetails.length > 1) position = getPositionDetails(errorDetails[1]) } opt.errLine = position.errLine opt.errCol = position.errCol opt.errFile = position.errFile.trim() - const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning' + const classList = + opt.type === "error" ? "alert alert-danger" : "alert alert-warning" return (
- await handlePointToErrorOnClick(opt.location, opt.fileName)} style={{ cursor: "pointer", overflow: 'hidden', textOverflow: 'ellipsis' }}> - {opt.name} - { opt.item.warning } - {opt.item.more - ? more - : +
+ await handlePointToErrorOnClick(opt.location, opt.fileName) } - - Pos: {opt.locationString} - - + style={{ + cursor: "pointer", + overflow: "hidden", + textOverflow: "ellipsis", + }} + > + {opt.name} + {opt.item.warning} + {opt.item.more ? ( + + + more + + + ) : ( + + )} +
+ + Pos: {opt.locationString} + +
+
) diff --git a/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts b/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts index 1ace7d5ff3..912b8982f8 100644 --- a/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts +++ b/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts @@ -56,73 +56,82 @@ slitherEnabled: boolean, setStartAnalysis: React.Dispatch { - groupedModules[key].forEach(el => { - if (el.name === result.name) { - moduleName = groupedModules[key][0].categoryDisplayName - } + if (basicEnabled) { + _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 + // 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]) + 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] } - 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 + 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, state.file, locationString) + const options = { + type: 'warning', + useSpan: true, + errFile: state.file, + 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(warningMessage) } - warningErrors.push(options) - warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName }) - setSsaWarnings(warningMessage) } - } + } else { + setSsaWarnings([]) + } // Slither Analysis if (showSlither && slitherEnabled) { setSlitherWarnings([]) @@ -198,12 +207,12 @@ slitherEnabled: boolean, setStartAnalysis: React.Dispatch { onChange={() => {}} /> - ) - } + ); + }; const categorySection = (category, categoryId, i) => { return ( @@ -416,8 +417,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { - ) - } + ); + }; useEffect(() => { if (!hideWarnings && !showLibsWarning) { @@ -436,85 +437,95 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { props.event.trigger("staticAnaysisWarning", [ slitherTotal + ssaTotal + hintsTotal === 0 ? -1 - : slitherTotal + ssaTotal + hintsTotal, - ]) + : !solhintEnabled && !basicEnabled && !slitherEnabled ? -1 : slitherTotal + ssaTotal + hintsTotal, + ]); } if (!hideWarnings && showLibsWarning) { props.event.trigger("staticAnaysisWarning", [ slitherWarnings.length + ssaWarnings.length + hints.length === 0 ? -1 - : slitherWarnings.length + ssaWarnings.length + hints.length, - ]) + : !solhintEnabled && !basicEnabled && !slitherEnabled ? -1 : slitherWarnings.length + ssaWarnings.length + hints.length, + ]); } if (hideWarnings) { const slitherTotal = slitherWarnings && state.data && state.source !== null ? slitherWarnings.filter((x) => x.options.type === "error").length - : 0 + : 0; const ssaTotal = ssaWarnings && state.data && state.source !== null ? ssaWarnings.filter((x) => x.options.type === "error").length - : 0 + : 0; const hintsTotal = hints && state.data && state.source !== null ? hints.filter((x) => x.type === "error").length - : 0 + : 0; props.event.trigger("staticAnaysisWarning", [ slitherTotal + ssaTotal + hintsTotal === 0 ? -1 : slitherTotal + ssaTotal + hintsTotal, - ]) + ]); + } + if (hideWarnings && !solhintEnabled && !slitherEnabled && !basicEnabled) { + props.event.trigger("staticAnaysisWarning", [-1]) } }, [hideWarnings, showLibsWarning]) useEffect(() => { - const slitherTotal = - slitherEnabled && + let slitherTotal = 0 + if (slitherEnabled && showSlither && slitherWarnings && state.data && - state.source !== null - ? slitherWarnings.filter((x) => !x.options.isLibrary && x.hasWarning) - .length - : 0 - const ssaTotal = - basicEnabled && ssaWarnings && state.data && state.source !== null - ? ssaWarnings.filter((x) => !x.options.isLibrary && x.hasWarning).length - : 0 - const hintsTotal = - solhintEnabled && hints && state.data && state.source !== null - ? hints.length - : 0 + state.source !== null) { + slitherTotal = slitherWarnings.filter((x) => !x.options.isLibrary && x.hasWarning).length + props.analysisModule.slitherEnabled = true + } + let ssaTotal = 0 + if (basicEnabled && ssaWarnings && state.data && state.source !== null) { + ssaTotal = ssaWarnings.filter((x) => !x.options.isLibrary && x.hasWarning).length + props.analysisModule.basicEnabled = true + } + + let hintsTotal = 0 + if (solhintEnabled && hints && state.data && state.source !== null) { + hintsTotal = hints.length + props.analysisModule.solhintEnabled = true + } props.event.trigger("staticAnaysisWarning", [ slitherTotal + ssaTotal + hintsTotal === 0 ? -1 : slitherTotal + ssaTotal + hintsTotal, - ]) + ]); }, [hints.length, slitherWarnings.length, ssaWarnings.length]) useEffect(() => { - const slitherTotal = - slitherWarnings && + let slitherTotal = 0 + if (slitherWarnings && slitherEnabled && showSlither && state.data && - state.source !== null - ? slitherWarnings.filter((x) => !x.options.isLibrary && x.hasWarning) - .length - : 0 - const ssaTotal = - ssaWarnings && basicEnabled && state.data && state.source !== null - ? ssaWarnings.filter((x) => !x.options.isLibrary && x.hasWarning).length - : 0 - const hintsTotal = - hints && solhintEnabled && state.data && state.source !== null - ? hints.length - : 0 + state.source !== null) { + slitherTotal = slitherWarnings.filter((x) => !x.options.isLibrary && x.hasWarning) + .length + props.analysisModule.slitherEnabled = true + } + + let ssaTotal = 0 + if (ssaWarnings && basicEnabled && state.data && state.source !== null) { + ssaTotal = ssaWarnings.filter((x) => !x.options.isLibrary && x.hasWarning).length + props.analysisModule.basicEnabled = true + } + + let hintsTotal = 0 + if (hints && solhintEnabled && state.data && state.source !== null) { + hintsTotal = hints.length + } props.event.trigger("staticAnaysisWarning", [ slitherTotal + ssaTotal + hintsTotal === 0 ? -1 : slitherTotal + ssaTotal + hintsTotal, - ]); + ]) }, [solhintEnabled, basicEnabled, slitherEnabled, showSlither]) const handleSlitherEnabled = async () => { @@ -524,36 +535,42 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { "remixd" ); if (showSlither) { - setShowSlither(false); + setShowSlither(false) + props.analysisModule.slitherEnabled = false } if (!showSlither) { - setShowSlither(true); + setShowSlither(true) + props.analysisModule.slitherEnabled = true } }; const handleBasicEnabled = () => { if (basicEnabled) { - setBasicEnabled(false); + setBasicEnabled(false) + props.analysisModule.basicEnabled = false if (solhintEnabled) { - setSelectedTab("solhint"); + setSelectedTab("solhint") } - props.event.trigger("staticAnalysisWarning", [-1]); + props.event.trigger("staticAnalysisWarning", [-1]) } else { - setBasicEnabled(true); - props.event.trigger("staticAnalysisWarning", [-1]); + setBasicEnabled(true) + props.analysisModule.basicEnabled = true + props.event.trigger("staticAnalysisWarning", [-1]) } }; const handleLinterEnabled = () => { if (solhintEnabled) { setSolhintEnabled(false); + props.analysisModule.solhintEnabled = false if (basicEnabled) { - setSelectedTab("remix"); + setSelectedTab("remix") } - props.event.trigger("staticAnalysisWarning", [-1]); + props.event.trigger("staticAnalysisWarning", [-1]) } else { - setSolhintEnabled(true); - props.event.trigger("staticAnalysisWarning", [-1]); + setSolhintEnabled(true) + props.analysisModule.solhintEnabled = true + props.event.trigger("staticAnalysisWarning", [-1]) } }; @@ -623,11 +640,70 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { {hint.formattedMessage}
- {`${hint.column}:${hint.line}`} + + {`Pos: ${hint.column}:${hint.line}`} + )) - : hintErrors.map((hint, index) => ( + : !hideWarnings && !showLibsWarning && !basicEnabled && solhintEnabled ? hints.map((hint, index) => ( +
{ + 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 } + ); + }} + > +
+ + {hint.formattedMessage} + +
+ + {`Pos: ${hint.column}:${hint.line}`} + +
+
+ )) : hintErrors.map((hint, index) => (
{ {hint.formattedMessage}
- {`${hint.column}:${hint.line}`} + + {`Pos: ${hint.column}:${hint.line}`} +
))} @@ -733,7 +816,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { ), child: ( <> - {Object.entries(warningState).length > 0 ? ( + {ssaWarnings.length > 0 ? (
@@ -751,6 +834,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { message={x.msg} opt={x.options} warningErrors={""} + ssaState={state} editor={props.analysisModule} />
@@ -767,11 +851,29 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { message={x.msg} opt={x.options} warningErrors={""} + ssaState={state} editor={props.analysisModule} />
)) - : null} + : !hideWarnings && !showLibsWarning && basicEnabled + ? ssaWarnings.filter((x) => !x.options.isLibrary && x.hasWarning) + .map((x, i) => ( +
+ +
+ )) : null}
@@ -838,6 +940,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { name={`staticAnalysisModule${warning.warningModuleName}${index}`} message={warning.msg} opt={warning.options} + ssaState={state} warningErrors={""} editor={props.analysisModule} /> @@ -854,6 +957,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { message={warning.msg} opt={warning.options} warningErrors={""} + ssaState={state} editor={props.analysisModule} /> @@ -870,6 +974,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { name={`staticAnalysisModule${warning.warningModuleName}${index}`} message={warning.msg} opt={warning.options} + ssaState={state} warningErrors={""} editor={props.analysisModule} /> @@ -919,7 +1024,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { .map((value: any) => { return value.map((x) => { return x._index.toString(); - }) + }); }) .flat() .every((el) => categoryIndex.includes(el)) @@ -1007,7 +1112,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { buttonText={`Analyze ${state.file}`} title={`${runButtonTitle}`} classList="btn btn-sm btn-primary btn-block" - onClick={async () => + onClick={async () => { await run( state.data, state.source, @@ -1035,7 +1140,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { solhintEnabled, basicEnabled ) - } + }} disabled={ state.data === null || !isSupportedVersion || @@ -1043,10 +1148,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { } /> )} - {state && - state.data !== null && - state.source !== null && - state.file.length > 0 ? ( + {ssaWarnings.length > 0 || hints.length > 0 ? (
{slitherWarnings.length > 0 || hints.length > 0 || @@ -1070,7 +1172,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
) : null}
{ name="hideWarnings" inputType="checkbox" checked={hideWarnings} - label="Hide warnings" + label="Show errors only" onClick={handleHideWarnings} onChange={() => {}} /> @@ -1101,7 +1203,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { setSelectedTab(tabKey); }} > -
- ); -}; + ) +} export default RemixUiStaticAnalyser; diff --git a/libs/remix-ui/static-analyser/src/staticanalyser.d.ts b/libs/remix-ui/static-analyser/src/staticanalyser.d.ts index af5de925ce..91ab488009 100644 --- a/libs/remix-ui/static-analyser/src/staticanalyser.d.ts +++ b/libs/remix-ui/static-analyser/src/staticanalyser.d.ts @@ -12,6 +12,9 @@ export declare class AnalysisTab extends ViewPlugin { column: number; line: number; }[] + basicEnabled: boolean; + solhintEnabled: boolean; + slitherEnabled: boolean; internalCount: number registry: Registry; element: HTMLDivElement;