finish markup for linter tab

pull/3642/head
Joseph Izang 1 year ago
parent 4435381141
commit c93dc2654c
  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 { Fade } from 'react-bootstrap'
import { AnalysisTab } from '../staticanalyser'
import { run } from './actions/staticAnalysisActions'
import SolHintTabChild from './components/SolHintTabChild'
declare global {
interface Window {
@ -66,8 +68,9 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
return indexOfCategory
}
const [autoRun, setAutoRun] = useState(true)
const [basicEnabled, setBasicEnabled] = useState(true)
const [slitherEnabled, setSlitherEnabled] = useState(false)
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
@ -106,9 +109,10 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect(() => {
setWarningState({})
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) {
runAnalysis().catch(console.error);
}
@ -217,159 +221,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
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 index = groupedModuleIndex(groupedModules)
if (index.every(el => categoryIndex.includes(el))) {
@ -404,11 +256,11 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
}
const handleAutoRun = () => {
if (autoRun) {
setAutoRun(false)
const handleBasicEnabled = () => {
if (basicEnabled) {
setBasicEnabled(false)
} 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 = () => {
}
@ -509,42 +351,33 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const tabKeys = [
{
tabKey: 'linter',
child: <div></div>,
child: <SolHintTabChild analysisModule={props.analysisModule} currentFile={state.file} />,
title: 'Linter'
},
{
tabKey: 'basic',
title: 'Basic',
child: <>
<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>
{Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
<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>
}
{Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
<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>
}
</>
},
{
@ -564,10 +397,10 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
inputType="checkbox"
title="Run solhint static analysis on file save"
onClick={handleShowLinterMessages}
checked={autoRun}
checked={solhintEnabled}
label="Linter"
onChange={() => {}}
tooltipPlacement={'bottom-start'}
tooltipPlacement={'top-start'}
/>
<RemixUiCheckbox
id="checkAllEntries"
@ -597,7 +430,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
buttonText="Analyse"
title={runButtonTitle}
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 }
/>
<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 Registry from '../state/registry';
export declare class AnalysisTab extends ViewPlugin {
event: any;
event: EventManager;
events: EventEmitter;
registry: Registry;
element: HTMLDivElement;
@ -30,8 +30,55 @@ type RemixUiStaticAnalyserState = {
version?: string
}
type SolHintReport = {
column: number,
line: number,
type: 'warning' | 'error',
formattedMessage: string,
options?: ErrorRendererOptions
}
type SolHintTabChildProps = {
analysisModule: AnalysisTab
currentFile: string
}
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
}
}
}

Loading…
Cancel
Save