finish markup for linter tab

pull/5370/head
Joseph Izang 2 years ago
parent 0ea0da6da3
commit cf4304c781
  1. 171
      libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
  2. 53
      libs/remix-ui/static-analyser/src/lib/components/SolHintTabChild.tsx
  3. 240
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  4. 49
      libs/remix-ui/static-analyser/src/staticanalyser.d.ts

@ -20,3 +20,174 @@ export const compilation = (analysisModule: AnalysisTab,
) )
} }
} }
/**
* 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>}
*/
export async function run (lastCompilationResult, lastCompilationSource, currentFile, state, props, isSupportedVersion, slitherEnabled, categoryIndex, groupedModules, runner, _paq, message, showWarnings, allWarnings, warningContainer) {
console.log({ state, 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])
}
}
console.log({ allWarnings })
}

@ -0,0 +1,53 @@
import React, { useEffect, useState } from "react"
import { SolHintTabChildProps } from "../../staticanalyser"
import ErrorRenderer from "../ErrorRenderer"
export default function SolHintTabChild(props: SolHintTabChildProps) {
const [hints, setHints] = useState([])
console.log({ hints })
useEffect(() => {
props.analysisModule.on('solidity', 'compilationFinished',
async (fileName, source, languageVersion, data) => {
const hints = await props.analysisModule.call('solhint', 'lint', fileName)
setHints(prev => [...prev, ...hints])
})
}, [])
return (
<>
{hints.length > 0 &&
<div id='solhintlintingresult' >
<div className="mb-4">
{
(hints.map((hint, index) => {
const options = {
type: hint.type,
filename: props.currentFile,
errCol: hint.column,
errLine: hint.line,
warning: hint.formattedMessage,
locationString: `${hint.line}:${hint.column}`,
location: { start: { line: hint.line, column: hint.column } },
isLibrary: false,
hasWarnings: hint.type === 'warning',
items: {
location: '',
warning: hint.formattedMessage,
}
};
(
<div key={index}>
<div data-id={`staticAnalysisModulesolhintWarning`} id={`staticAnalysisModulesohint${hint.type}`}>
<ErrorRenderer name={`staticAnalysisModulesolhint${hint.type}${index}`} message={hint.formattedMessage} opt={options} warningErrors={ hint.formattedMessage} editor={props.analysisModule}/>
</div>
</div>
)}))
}
</div>
</div>
}
</>
)
}

@ -14,6 +14,8 @@ import Tab from 'react-bootstrap/Tab'
import Tabs from 'react-bootstrap/Tabs' import Tabs from 'react-bootstrap/Tabs'
import { Fade } from 'react-bootstrap' import { Fade } from 'react-bootstrap'
import { AnalysisTab } from '../staticanalyser' import { AnalysisTab } from '../staticanalyser'
import { run } from './actions/staticAnalysisActions'
import SolHintTabChild from './components/SolHintTabChild'
declare global { declare global {
interface Window { interface Window {
@ -66,8 +68,9 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
} }
return indexOfCategory return indexOfCategory
} }
const [autoRun, setAutoRun] = useState(true) const [basicEnabled, setBasicEnabled] = useState(true)
const [slitherEnabled, setSlitherEnabled] = useState(false) const [slitherEnabled, setSlitherEnabled] = useState(false)
const [solhintEnabled, setSolhintEnabled] = useState(true) // assuming that solhint is always enabled
const [showSlither, setShowSlither] = useState(false) const [showSlither, setShowSlither] = 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
@ -106,9 +109,10 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect(() => { useEffect(() => {
setWarningState({}) setWarningState({})
const runAnalysis = async () => { const runAnalysis = async () => {
await run(state.data, state.source, state.file) await run(state.data, state.source, state.file, allWarnings, props, isSupportedVersion, slitherEnabled, categoryIndex, groupedModules, runner,_paq,
message, showWarnings, allWarnings, warningContainer)
} }
if (autoRun) { if (basicEnabled) {
if (state.data !== null) { if (state.data !== null) {
runAnalysis().catch(console.error); runAnalysis().catch(console.error);
} }
@ -217,159 +221,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
filterWarnings() filterWarnings()
} }
const run = async (lastCompilationResult, lastCompilationSource, currentFile) => {
console.log({ state, lastCompilationResult, lastCompilationSource, currentFile })
if (!isSupportedVersion) return
if (state.data !== null) {
if (lastCompilationResult && (categoryIndex.length > 0 || slitherEnabled)) {
const warningMessage = []
const warningErrors = []
const hints = await props.analysisModule.call('solhint', 'lint', currentFile)
console.log({ hints })
// 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 handleCheckAllModules = (groupedModules) => {
const index = groupedModuleIndex(groupedModules) const index = groupedModuleIndex(groupedModules)
if (index.every(el => categoryIndex.includes(el))) { if (index.every(el => categoryIndex.includes(el))) {
@ -404,11 +256,11 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
} }
} }
const handleAutoRun = () => { const handleBasicEnabled = () => {
if (autoRun) { if (basicEnabled) {
setAutoRun(false) setBasicEnabled(false)
} else { } else {
setAutoRun(true) setBasicEnabled(true)
} }
} }
@ -488,16 +340,6 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
) )
} }
useEffect(() => {
console.log('logging hints from solhint in useEffect')
props.analysisModule.on('solhint' as any, 'lintOnCompilationFinished',
(hints: any) => {
console.log({ hints })
})
return () => props.analysisModule.off('solhint' as any, 'lintOnCompilationFinished')
}, [])
const handleShowLinterMessages = () => { const handleShowLinterMessages = () => {
} }
@ -509,42 +351,33 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const tabKeys = [ const tabKeys = [
{ {
tabKey: 'linter', tabKey: 'linter',
child: <div></div>, child: <SolHintTabChild analysisModule={props.analysisModule} currentFile={state.file} />,
title: 'Linter' title: 'Linter'
}, },
{ {
tabKey: 'basic', tabKey: 'basic',
title: 'Basic', title: 'Basic',
child: <> child: <>
<div id="staticanalysismodules" className="list-group list-group-flush"> {Object.entries(warningState).length > 0 &&
{Object.keys(groupedModules).map((categoryId, i) => { <div id='staticanalysisresult' >
const category = groupedModules[categoryId] <div className="mb-4">
return ( {
categorySection(category, categoryId, i) (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
</div> x.hasWarning ? ( // eslint-disable-next-line dot-notation
{Object.entries(warningState).length > 0 && <div data-id={`staticAnalysisModule${x.warningModuleName}${i}`} id={`staticAnalysisModule${x.warningModuleName}${i}`} key={i}>
<div id='staticanalysisresult' > <ErrorRenderer name={`staticAnalysisModule${x.warningModuleName}${i}`} message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/>
<div className="mb-4"> </div>
{ ) : null
(Object.entries(warningState).map((element, index) => ( ))}
<div key={index}> </div>
{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>
<div data-id={`staticAnalysisModule${x.warningModuleName}${i}`} id={`staticAnalysisModule${x.warningModuleName}${i}`} key={i}> </div>
<ErrorRenderer name={`staticAnalysisModule${x.warningModuleName}${i}`} message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/> }
</div>
) : null
))}
</div>
)))
}
</div>
</div>
}
</> </>
}, },
{ {
@ -564,10 +397,10 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
inputType="checkbox" inputType="checkbox"
title="Run solhint static analysis on file save" title="Run solhint static analysis on file save"
onClick={handleShowLinterMessages} onClick={handleShowLinterMessages}
checked={autoRun} checked={solhintEnabled}
label="Linter" label="Linter"
onChange={() => {}} onChange={() => {}}
tooltipPlacement={'bottom-start'} tooltipPlacement={'top-start'}
/> />
<RemixUiCheckbox <RemixUiCheckbox
id="checkAllEntries" id="checkAllEntries"
@ -597,7 +430,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
buttonText="Analyse" buttonText="Analyse"
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)} onClick={async () => await run(state.data, state.source, state.file, allWarnings, props, isSupportedVersion, slitherEnabled, categoryIndex, groupedModules, runner,_paq,
message, showWarnings, allWarnings, warningContainer)}
disabled={(state.data === null || categoryIndex.length === 0) && !slitherEnabled || !isSupportedVersion } disabled={(state.data === null || categoryIndex.length === 0) && !slitherEnabled || !isSupportedVersion }
/> />
<div className="mt-4 p-2 d-flex border-top flex-column"> <div className="mt-4 p-2 d-flex border-top flex-column">

@ -4,7 +4,7 @@ import { ViewPlugin } from '@remixproject/engine-web';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import Registry from '../state/registry'; import Registry from '../state/registry';
export declare class AnalysisTab extends ViewPlugin { export declare class AnalysisTab extends ViewPlugin {
event: any; event: EventManager;
events: EventEmitter; events: EventEmitter;
registry: Registry; registry: Registry;
element: HTMLDivElement; element: HTMLDivElement;
@ -30,8 +30,55 @@ type RemixUiStaticAnalyserState = {
version?: string version?: string
} }
type SolHintReport = {
column: number,
line: number,
type: 'warning' | 'error',
formattedMessage: string,
options?: ErrorRendererOptions
}
type SolHintTabChildProps = {
analysisModule: AnalysisTab
currentFile: string
}
type RemixUiStaticAnalyserReducerActionType = { type RemixUiStaticAnalyserReducerActionType = {
type: 'compilationFinished' | '' | any, type: 'compilationFinished' | '' | any,
payload: RemixUiStaticAnalyserState 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
}
}
}

Loading…
Cancel
Save