Merge pull request #1104 from ethereum/refactoring-static-analyser

refactor: static analyser plugin from yo-yo to react
pull/5370/head
David Disu 4 years ago committed by GitHub
commit 4485a9d3c5
  1. 2
      apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts
  2. 58
      apps/remix-ide/src/app/tabs/analysis-tab.js
  3. 302
      apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js
  4. 36
      apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js
  5. 4
      libs/remix-ui/checkbox/.babelrc
  6. 19
      libs/remix-ui/checkbox/.eslintrc
  7. 7
      libs/remix-ui/checkbox/README.md
  8. 1
      libs/remix-ui/checkbox/src/index.ts
  9. 0
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.css
  10. 47
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  11. 16
      libs/remix-ui/checkbox/tsconfig.json
  12. 13
      libs/remix-ui/checkbox/tsconfig.lib.json
  13. 4
      libs/remix-ui/static-analyser/.babelrc
  14. 19
      libs/remix-ui/static-analyser/.eslintrc
  15. 7
      libs/remix-ui/static-analyser/README.md
  16. 1
      libs/remix-ui/static-analyser/src/index.ts
  17. 21
      libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
  18. 65
      libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
  19. 14
      libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
  20. 21
      libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts
  21. 351
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  22. 16
      libs/remix-ui/static-analyser/tsconfig.json
  23. 13
      libs/remix-ui/static-analyser/tsconfig.lib.json
  24. 6
      nx.json
  25. 3
      package.json
  26. 6
      tsconfig.json
  27. 35
      workspace.json

@ -40,7 +40,7 @@ function runTests (browser: NightwatchBrowser) {
.pause(10000)
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TooMuchGas', 'test1', 'test2'])
.clickLaunchIcon('solidityStaticAnalysis')
.click('#staticanalysisView button')
.click('#staticanalysisButton button')
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
listSelectorContains(['Use of tx.origin',
'Fallback function of contract TooMuchGas requires too much gas',

@ -1,9 +1,11 @@
import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
import ReactDOM from 'react-dom'
import { EventEmitter } from 'events'
import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
var Renderer = require('../ui/renderer')
var yo = require('yo-yo')
var StaticAnalysis = require('./staticanalysis/staticAnalysisView')
var EventManager = require('../../lib/events')
const profile = {
@ -25,23 +27,49 @@ class AnalysisTab extends ViewPlugin {
this.event = new EventManager()
this.events = new EventEmitter()
this.registry = registry
this.element = document.createElement('div')
this.element.setAttribute('id', 'staticAnalyserView')
this._components = {
renderer: new Renderer(this)
}
this._components.registry = this.registry
this._deps = {
offsetToLineColumnConverter: this.registry.get(
'offsettolinecolumnconverter').api
}
}
onActivation () {
this.renderComponent()
}
render () {
this.staticanalysis = new StaticAnalysis(this.registry, this)
this.staticanalysis.event.register('staticAnaysisWarning', (count) => {
if (count > 0) {
this.emit('statusChanged', { key: count, title: `${count} warning${count === 1 ? '' : 's'}`, type: 'warning' })
} else if (count === 0) {
this.emit('statusChanged', { key: 'succeed', title: 'no warning', type: 'success' })
} else {
// count ==-1 no compilation result
this.emit('statusChanged', { key: 'none' })
}
})
this.registry.put({ api: this.staticanalysis, name: 'staticanalysis' })
return this.element
}
return yo`<div class="px-3 pb-1" id="staticanalysisView">${this.staticanalysis.render()}</div>`
renderComponent () {
ReactDOM.render(
<RemixUiStaticAnalyser
analysisRunner={this.runner}
registry={this.registry}
staticanalysis={this.staticanalysis}
analysisModule={this}
event={this.event}
/>,
this.element,
() => {
this.event.register('staticAnaysisWarning', (count) => {
if (count > 0) {
this.emit('statusChanged', { key: count, title: `${count} warning${count === 1 ? '' : 's'}`, type: 'warning' })
} else if (count === 0) {
this.emit('statusChanged', { key: 'succeed', title: 'no warning', type: 'success' })
} else {
// count ==-1 no compilation result
this.emit('statusChanged', { key: 'none' })
}
})
}
)
}
}

@ -1,302 +0,0 @@
'use strict'
var StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
var yo = require('yo-yo')
var $ = require('jquery')
var remixLib = require('@remix-project/remix-lib')
var utils = remixLib.util
var css = require('./styles/staticAnalysisView-styles')
var Renderer = require('../../ui/renderer')
const SourceHighlighter = require('../../editor/sourceHighlighter')
var EventManager = require('../../../lib/events')
function staticAnalysisView (localRegistry, analysisModule) {
var self = this
this.event = new EventManager()
this.view = null
this.runner = new StaticAnalysisRunner()
this.modulesView = this.renderModules()
this.lastCompilationResult = null
this.lastCompilationSource = null
this.currentFile = 'No file compiled'
this.sourceHighlighter = new SourceHighlighter()
this.analysisModule = analysisModule
self._components = {
renderer: new Renderer(analysisModule)
}
self._components.registry = localRegistry
// dependencies
self._deps = {
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api
}
analysisModule.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
self.lastCompilationResult = null
self.lastCompilationSource = null
if (languageVersion.indexOf('soljson') !== 0) return
self.lastCompilationResult = data
self.lastCompilationSource = source
self.currentFile = file
self.correctRunBtnDisabled()
if (self.view && self.view.querySelector('#autorunstaticanalysis').checked) {
self.run()
}
})
}
staticAnalysisView.prototype.render = function () {
this.runBtn = yo`<button class="btn btn-sm w-25 btn-primary" onclick="${() => { this.run() }}" >Run</button>`
const view = yo`
<div class="${css.analysis}">
<div class="my-2 d-flex flex-column align-items-left">
<div class="${css.top} d-flex justify-content-between">
<div class="pl-2 ${css.label}" for="checkAllEntries">
<input id="checkAllEntries"
type="checkbox"
onclick="${(event) => { this.checkAll(event) }}"
style="vertical-align:bottom"
checked="true"
>
<label class="text-nowrap pl-2 mb-0" for="checkAllEntries">
Select all
</label>
</div>
<div class="${css.label}" for="autorunstaticanalysis">
<input id="autorunstaticanalysis"
type="checkbox"
style="vertical-align:bottom"
checked="true"
>
<label class="text-nowrap pl-2 mb-0" for="autorunstaticanalysis">
Autorun
</label>
</div>
${this.runBtn}
</div>
</div>
<div id="staticanalysismodules" class="list-group list-group-flush">
${this.modulesView}
</div>
<div class="mt-2 p-2 d-flex border-top flex-column">
<span>last results for:</span>
<span class="text-break break-word word-break font-weight-bold" id="staticAnalysisCurrentFile">${this.currentFile}</span>
</div>
<div class="${css.result} my-1" id='staticanalysisresult'></div>
</div>
`
if (!this.view) {
this.view = view
}
this.correctRunBtnDisabled()
return view
}
staticAnalysisView.prototype.selectedModules = function () {
if (!this.view) {
return []
}
const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
var toRun = []
for (var i = 0; i < selected.length; i++) {
toRun.push(selected[i].attributes.index.value)
}
return toRun
}
staticAnalysisView.prototype.run = function () {
if (!this.view) {
return
}
const highlightLocation = async (location, fileName) => {
await this.analysisModule.call('editor', 'discardHighlight')
await this.analysisModule.call('editor', 'highlight', location, fileName)
}
const selected = this.selectedModules()
const warningContainer = $('#staticanalysisresult')
warningContainer.empty()
this.view.querySelector('#staticAnalysisCurrentFile').innerText = this.currentFile
var self = this
if (this.lastCompilationResult && selected.length) {
this.runBtn.removeAttribute('disabled')
let warningCount = 0
this.runner.run(this.lastCompilationResult, selected, (results) => {
const groupedModules = utils.groupBy(preProcessModules(this.runner.modules()), 'categoryId')
results.map((result, j) => {
let moduleName
Object.keys(groupedModules).map((key) => {
groupedModules[key].forEach((el) => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
const alreadyExistedEl = this.view.querySelector(`[id="staticAnalysisModule${moduleName}"]`)
if (!alreadyExistedEl) {
warningContainer.append(`
<div class="mb-4" name="staticAnalysisModules" id="staticAnalysisModule${moduleName}">
<span class="text-dark h6">${moduleName}</span>
</div>
`)
}
result.report.map((item, i) => {
let location = ''
let locationString = 'not available'
let column = 0
let row = 0
let fileName = this.currentFile
if (item.location) {
var split = item.location.split(':')
var file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = self._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
self.lastCompilationSource.sources,
self.lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = (row + 1) + ':' + column + ':'
fileName = Object.keys(self.lastCompilationResult.contracts)[file]
}
warningCount++
const msg = yo`
<span class="d-flex flex-column">
<span class="h6 font-weight-bold">${result.name}</span>
${item.warning}
${item.more ? yo`<span><a href="${item.more}" target="_blank">more</a></span>` : yo`<span></span>`}
<span class="" title="Position in ${fileName}">Pos: ${locationString}</span>
</span>`
self._components.renderer.error(
msg,
this.view.querySelector(`[id="staticAnalysisModule${moduleName}"]`),
{
click: () => highlightLocation(location, fileName),
type: 'warning',
useSpan: true,
errFile: fileName,
errLine: row,
errCol: column
}
)
})
})
// hide empty staticAnalysisModules sections
this.view.querySelectorAll('[name="staticAnalysisModules"]').forEach((section) => {
if (!section.getElementsByClassName('alert-warning').length) section.hidden = true
})
self.event.trigger('staticAnaysisWarning', [warningCount])
})
} else {
this.runBtn.setAttribute('disabled', 'disabled')
if (selected.length) {
warningContainer.html('No compiled AST available')
}
self.event.trigger('staticAnaysisWarning', [-1])
}
}
staticAnalysisView.prototype.checkModule = function (event) {
const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
const checkAll = this.view.querySelector('[id="checkAllEntries"]')
this.correctRunBtnDisabled()
if (event.target.checked) {
checkAll.checked = true
} else if (!selected.length) {
checkAll.checked = false
}
}
staticAnalysisView.prototype.correctRunBtnDisabled = function () {
if (!this.view) {
return
}
const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
if (this.lastCompilationResult && selected.length !== 0) {
this.runBtn.removeAttribute('disabled')
} else {
this.runBtn.setAttribute('disabled', 'disabled')
}
}
staticAnalysisView.prototype.checkAll = function (event) {
if (!this.view) {
return
}
// checks/unchecks all
const checkBoxes = this.view.querySelectorAll('[name="staticanalysismodule"]')
checkBoxes.forEach((checkbox) => { checkbox.checked = event.target.checked })
this.correctRunBtnDisabled()
}
staticAnalysisView.prototype.handleCollapse = function (e) {
const downs = e.toElement.parentElement.getElementsByClassName('fas fa-angle-double-right')
const iEls = document.getElementsByTagName('i')
for (var i = 0; i < iEls.length; i++) { iEls[i].hidden = false }
downs[0].hidden = true
}
staticAnalysisView.prototype.renderModules = function () {
const groupedModules = utils.groupBy(preProcessModules(this.runner.modules()), 'categoryId')
const moduleEntries = Object.keys(groupedModules).map((categoryId, i) => {
const category = groupedModules[categoryId]
const entriesDom = category.map((item, i) => {
return yo`
<div class="form-check">
<input id="staticanalysismodule_${categoryId}_${i}"
type="checkbox"
class="form-check-input staticAnalysisItem"
name="staticanalysismodule"
index=${item._index}
checked="true"
style="vertical-align:bottom"
onclick="${(event) => this.checkModule(event)}"
>
<label for="staticanalysismodule_${categoryId}_${i}" class="form-check-label mb-1">
<p class="mb-0 font-weight-bold text-capitalize">${item.name}</p>
${item.description}
</label>
</div>
`
})
return yo`
<div class="${css.block}">
<input type="radio" name="accordion" class="w-100 d-none card" id="heading${categoryId}" onclick=${(e) => this.handleCollapse(e)}"/>
<label for="heading${categoryId}" style="cursor: pointer;" class="pl-3 card-header h6 d-flex justify-content-between font-weight-bold border-left px-1 py-2 w-100">
${category[0].categoryDisplayName}
<div>
<i class="fas fa-angle-double-right"></i>
</div>
</label>
<div class="w-100 d-block px-2 my-1 ${css.entries}">
${entriesDom}
</div>
</div>
`
})
// collaps first module
moduleEntries[0].getElementsByTagName('input')[0].checked = true
moduleEntries[0].getElementsByTagName('i')[0].hidden = true
return yo`
<div class="accordion" id="accordionModules">
${moduleEntries}
</div>`
}
module.exports = staticAnalysisView
/**
* @dev Process & categorize static analysis modules to show them on UI
* @param arr list of static analysis modules received from remix-analyzer module
*/
function preProcessModules (arr) {
return arr.map((Item, i) => {
const itemObj = new Item()
itemObj._index = i
itemObj.categoryDisplayName = itemObj.category.displayName
itemObj.categoryId = itemObj.category.id
return itemObj
})
}

@ -1,36 +0,0 @@
var csjs = require('csjs-inject')
var css = csjs`
.analysis {
display: flex;
flex-direction: column;
}
.result {
margin-top: 1%;
max-height: 300px;
word-break: break-word;
}
.buttons {
margin: 1rem 0;
}
.label {
display: flex;
align-items: center;
}
.label {
display: flex;
align-items: center;
user-select: none;
}
.block input[type='radio']:checked ~ .entries{
height: auto;
transition: .5s ease-in;
}
.entries{
height: 0;
overflow: hidden;
transition: .5s ease-out;
}
`
module.exports = css

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-checkbox
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-checkbox` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-checkbox'

@ -0,0 +1,47 @@
import React from 'react' //eslint-disable-line
import './remix-ui-checkbox.css'
/* eslint-disable-next-line */
export interface RemixUiCheckboxProps {
onClick?: (event) => void
onChange?: (event) => void
label?: string
inputType?: string
name?: string
checked?: boolean
id?: string
itemName?: string
categoryId?: string
}
export const RemixUiCheckbox = ({
id,
label,
onClick,
inputType,
name,
checked,
onChange,
itemName,
categoryId
}: RemixUiCheckboxProps) => {
return (
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: 'flex', alignItems: 'center' }} onClick={onClick}>
<input
id={id}
type={inputType}
onChange={onChange}
style={{ verticalAlign: 'bottom' }}
name={name}
className="custom-control-input"
checked={checked}
/>
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }}>
{name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label}
</label>
</div>
)
}
export default RemixUiCheckbox

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

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

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-static-analyser
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-static-analyser` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-static-analyser'

@ -0,0 +1,21 @@
import React from 'react' //eslint-disable-line
interface StaticAnalyserButtonProps {
onClick: (event) => void
buttonText: string,
disabled?: boolean
}
const StaticAnalyserButton = ({
onClick,
buttonText,
disabled
}: StaticAnalyserButtonProps) => {
return (
<button className="btn btn-sm w-25 btn-primary" onClick={onClick} disabled={disabled}>
{buttonText}
</button>
)
}
export default StaticAnalyserButton

@ -0,0 +1,65 @@
import React from 'react' //eslint-disable-line
interface ErrorRendererProps {
message: any;
opt: any,
warningErrors: any
editor: any
}
const ErrorRenderer = ({ message, opt, editor }: ErrorRendererProps) => {
const getPositionDetails = (msg: any) => {
const result = { } as Record<string, number | string>
// To handle some compiler warning without location like SPDX license warning etc
if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg }
// extract line / column
let position = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/)
result.errLine = position ? parseInt(position[2]) - 1 : -1
result.errCol = position ? parseInt(position[3]) : -1
// extract file
position = msg.match(/^(https:.*?|http:.*?|.*?):/)
result.errFile = position ? position[1] : ''
return result
}
const handlePointToErrorOnClick = (location, fileName) => {
editor.call('editor', 'discardHighlight')
editor.call('editor', 'highlight', location, fileName)
}
if (!message) return
let position = getPositionDetails(message)
if (!position.errFile || (opt.errorType && opt.errorType === position.errFile)) {
// Updated error reported includes '-->' before file details
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'
return (
<div>
<div className={`sol ${opt.type} ${classList}`}>
<div className="close" data-id="renderer">
<i className="fas fa-times"></i>
</div>
<span className='d-flex flex-column' onClick={() => handlePointToErrorOnClick(opt.location, opt.fileName)}>
<span className='h6 font-weight-bold'>{opt.name}</span>
{ opt.item.warning }
{opt.item.more
? <span><a href={opt.item.more} target='_blank'>more</a></span>
: <span> </span>
}
<span title={`Position in ${opt.errFile}`}>Pos: {opt.locationString}</span>
</span>
</div>
</div>
)
}
export default ErrorRenderer

@ -0,0 +1,14 @@
import React from 'react' //eslint-disable-line
export const compilation = (analysisModule, dispatch) => {
if (analysisModule) {
analysisModule.on(
'solidity',
'compilationFinished',
(file, source, languageVersion, data) => {
if (languageVersion.indexOf('soljson') !== 0) return
dispatch({ type: 'compilationFinished', payload: { file, source, languageVersion, data } })
}
)
}
}

@ -0,0 +1,21 @@
export const initialState = {
file: null,
source: null,
languageVersion: null,
data: null
}
export const analysisReducer = (state, action) => {
switch (action.type) {
case 'compilationFinished':
return {
...state,
file: action.payload.file,
source: action.payload.source,
languageVersion: action.payload.languageVersion,
data: action.payload.data
}
default:
return initialState
}
}

@ -0,0 +1,351 @@
import React, { useEffect, useState, useReducer } from 'react'
import Button from './Button/StaticAnalyserButton' // eslint-disable-line
import remixLib from '@remix-project/remix-lib'
import _ from 'lodash'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import { RemixUiCheckbox } from '@remix-ui/checkbox' // eslint-disable-line
import ErrorRenderer from './ErrorRenderer' // eslint-disable-line
import { compilation } from './actions/staticAnalysisActions'
import { initialState, analysisReducer } from './reducers/staticAnalysisReducer'
const StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
const utils = remixLib.util
/* eslint-disable-next-line */
export interface RemixUiStaticAnalyserProps {
registry: any,
event: any,
analysisModule: any
}
export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const [runner] = useState(new StaticAnalysisRunner())
const preProcessModules = (arr: any) => {
return arr.map((Item, i) => {
const itemObj = new Item()
itemObj._index = i
itemObj.categoryDisplayName = itemObj.category.displayName
itemObj.categoryId = itemObj.category.id
return itemObj
})
}
const groupedModules = utils.groupBy(
preProcessModules(runner.modules()),
'categoryId'
)
const getIndex = (modules, array) => {
Object.values(modules).map((value: {_index}) => {
if (Array.isArray(value)) {
value.forEach((x) => {
array.push(x._index.toString())
})
} else {
array.push(value._index.toString())
}
})
}
const groupedModuleIndex = (modules) => {
const indexOfCategory = []
if (!_.isEmpty(modules)) {
getIndex(modules, indexOfCategory)
}
return indexOfCategory
}
const [autoRun, setAutoRun] = useState(true)
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
const warningContainer = React.useRef(null)
const [warningState, setWarningState] = useState([])
const [state, dispatch] = useReducer(analysisReducer, initialState)
useEffect(() => {
compilation(props.analysisModule, dispatch)
}, [])
useEffect(() => {
if (autoRun) {
if (state.data !== null) {
run(state.data, state.source, state.file)
}
}
return () => { }
}, [autoRun, categoryIndex, state])
const message = (name, warning, more, fileName, locationString) : string => {
return (`
<span className='d-flex flex-column'>
<span className='h6 font-weight-bold'>${name}</span>
${warning}
${more
? (<span><a href={more} target='_blank'>more</a></span>)
: (<span> </span>)
}
<span className="" title={Position in ${fileName}}>Pos: ${locationString}</span>
</span>`
)
}
const run = (lastCompilationResult, lastCompilationSource, currentFile) => {
if (state.data !== null) {
if (lastCompilationResult && categoryIndex.length > 0) {
let warningCount = 0
const warningMessage = []
runner.run(lastCompilationResult, categoryIndex, results => {
results.map((result) => {
let moduleName
Object.keys(groupedModules).map(key => {
groupedModules[key].forEach(el => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
const warningErrors = []
result.report.map((item) => {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
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.contracts)[file]
}
warningCount++
const msg = message(item.name, item.warning, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
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 })
})
})
const resultArray = []
warningMessage.map(x => {
resultArray.push(x)
})
function groupBy (objectArray, property) {
return objectArray.reduce((acc, obj) => {
const key = obj[property]
if (!acc[key]) {
acc[key] = []
}
// Add object to list for given key's value
acc[key].push(obj)
return acc
}, {})
}
const groupedCategory = groupBy(resultArray, 'warningModuleName')
setWarningState(groupedCategory)
})
if (categoryIndex.length > 0) {
props.event.trigger('staticAnaysisWarning', [warningCount])
}
} 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))) {
setCategoryIndex(
categoryIndex.filter((el) => {
return !index.includes(el)
})
)
} else {
setCategoryIndex(_.uniq([...categoryIndex, ...index]))
}
}
const handleCheckOrUncheckCategory = (category) => {
const index = groupedModuleIndex(category)
if (index.every(el => categoryIndex.includes(el))) {
setCategoryIndex(
categoryIndex.filter((el) => {
return !index.includes(el)
})
)
} else {
setCategoryIndex(_.uniq([...categoryIndex, ...index]))
}
}
const handleAutoRun = () => {
if (autoRun) {
setAutoRun(false)
} else {
setAutoRun(true)
}
}
const handleCheckSingle = (event, _index) => {
_index = _index.toString()
if (categoryIndex.includes(_index)) {
setCategoryIndex(categoryIndex.filter(val => val !== _index))
} else {
setCategoryIndex(_.uniq([...categoryIndex, _index]))
}
}
const categoryItem = (categoryId, item, i) => {
return (
<div className="form-check" key={i}>
<RemixUiCheckbox
categoryId={categoryId}
id={`staticanalysismodule_${categoryId}_${i}`}
inputType="checkbox"
name="checkSingleEntry"
itemName={item.name}
label={item.description}
onClick={event => handleCheckSingle(event, item._index)}
checked={categoryIndex.includes(item._index.toString())}
onChange={() => {}}
/>
</div>
)
}
const categorySection = (category, categoryId, i) => {
return (
<div className="" key={i}>
<div className="block">
<TreeView>
<TreeViewItem
label={
<label
htmlFor={`heading${categoryId}`}
style={{ cursor: 'pointer' }}
className="pl-3 card-header h6 d-flex justify-content-between font-weight-bold px-1 py-2 w-100"
data-bs-toggle="collapse"
data-bs-expanded="false"
data-bs-controls={`heading${categoryId}`}
data-bs-target={`#heading${categoryId}`}
>
{category[0].categoryDisplayName}
</label>
}
expand={false}
>
<div>
<RemixUiCheckbox onClick={() => handleCheckOrUncheckCategory(category)} id={categoryId} inputType="checkbox" label={`Select ${category[0].categoryDisplayName}`} name='checkCategoryEntry' checked={category.map(x => x._index.toString()).every(el => categoryIndex.includes(el))} onChange={() => {}}/>
</div>
<div className="w-100 d-block px-2 my-1 entries collapse multi-collapse" id={`heading${categoryId}`}>
{category.map((item, i) => {
return (
categoryItem(categoryId, item, i)
)
})}
</div>
</TreeViewItem>
</TreeView>
</div>
</div>
)
}
return (
<div className="analysis_3ECCBV px-3 pb-1">
<div className="my-2 d-flex flex-column align-items-left">
<div className="d-flex justify-content-between" id="staticanalysisButton">
<RemixUiCheckbox
id="checkAllEntries"
inputType="checkbox"
checked={Object.values(groupedModules).map((value: any) => {
return (value.map(x => {
return x._index.toString()
}))
}).flat().every(el => categoryIndex.includes(el))}
label="Select all"
onClick={() => handleCheckAllModules(groupedModules)}
onChange={() => {}}
/>
<RemixUiCheckbox
id="autorunstaticanalysis"
inputType="checkbox"
onClick={handleAutoRun}
checked={autoRun}
label="Autorun"
onChange={() => {}}
/>
<Button buttonText="Run" onClick={() => run(state.data, state.source, state.file)} disabled={state.data === null}/>
</div>
</div>
<div id="staticanalysismodules" className="list-group list-group-flush">
{Object.keys(groupedModules).map((categoryId, i) => {
const category = groupedModules[categoryId]
return (
categorySection(category, categoryId, i)
)
})
}
</div>
<div className="mt-2 p-2 d-flex border-top flex-column">
<span>last results for:</span>
<span
className="text-break break-word word-break font-weight-bold"
id="staticAnalysisCurrentFile"
>
{state.file}
</span>
</div>
{ categoryIndex.length > 0 && Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
<div className="mb-4">
{
(Object.entries(warningState).map((element) => (
<>
<span className="text-dark h6">{element[0]}</span>
{element[1].map(x => (
x.hasWarning ? (
<div id={`staticAnalysisModule${element[1].warningModuleName}`}>
<ErrorRenderer message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/>
</div>
) : null
))}
</>
)))
}
</div>
</div>
}
</div>
)
}
export default RemixUiStaticAnalyser

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

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

@ -95,6 +95,12 @@
},
"remix-ui-workspace": {
"tags": []
},
"remix-ui-static-analyser": {
"tags": []
},
"remix-ui-checkbox": {
"tags": []
}
}
}

@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs",
@ -159,6 +159,7 @@
"isbinaryfile": "^3.0.2",
"jquery": "^3.3.1",
"jszip": "^3.6.0",
"lodash": "^4.17.21",
"latest-version": "^5.1.0",
"lodash": "^4.17.21",
"merge": "^1.2.0",

@ -11,7 +11,7 @@
"target": "es2015",
"module": "commonjs",
"typeRoots": ["node_modules/@types"],
"lib": ["es2017", "dom"],
"lib": ["es2017", "es2019", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
@ -38,7 +38,9 @@
"@remix-ui/modal-dialog": ["libs/remix-ui/modal-dialog/src/index.ts"],
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"],
"@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/src/index.ts"],
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"]
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"],
"@remix-ui/static-analyser": ["libs/remix-ui/static-analyser/src/index.ts"],
"@remix-ui/checkbox": ["libs/remix-ui/checkbox/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -725,6 +725,41 @@
}
}
}
},
"remix-ui-static-analyser": {
"root": "libs/remix-ui/static-analyser",
"sourceRoot": "libs/remix-ui/static-analyser/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/static-analyser/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/static-analyser/**/*"
]
}
}
}
},
"remix-ui-checkbox": {
"root": "libs/remix-ui/checkbox",
"sourceRoot": "libs/remix-ui/checkbox/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/checkbox/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/checkbox/**/*"]
}
}
}
}
},
"cli": {

Loading…
Cancel
Save