diff --git a/apps/remix-ide/src/app/tabs/analysis-tab.js b/apps/remix-ide/src/app/tabs/analysis-tab.js
index 4406ba875e..ad3c2fce54 100644
--- a/apps/remix-ide/src/app/tabs/analysis-tab.js
+++ b/apps/remix-ide/src/app/tabs/analysis-tab.js
@@ -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,24 +27,39 @@ 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 yo`
${this.staticanalysis.render()}
`
+ return this.element
}
+
+ renderComponent () {
+ ReactDOM.render(
+ ,
+ this.element
+ )
+ }
+
}
module.exports = AnalysisTab
diff --git a/libs/remix-ui/static-analyser/.babelrc b/libs/remix-ui/static-analyser/.babelrc
new file mode 100644
index 0000000000..09d67939cc
--- /dev/null
+++ b/libs/remix-ui/static-analyser/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["@nrwl/react/babel"],
+ "plugins": []
+}
diff --git a/libs/remix-ui/static-analyser/.eslintrc b/libs/remix-ui/static-analyser/.eslintrc
new file mode 100644
index 0000000000..dae5c6feeb
--- /dev/null
+++ b/libs/remix-ui/static-analyser/.eslintrc
@@ -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"
+ }
+}
diff --git a/libs/remix-ui/static-analyser/README.md b/libs/remix-ui/static-analyser/README.md
new file mode 100644
index 0000000000..7e4b95c0e5
--- /dev/null
+++ b/libs/remix-ui/static-analyser/README.md
@@ -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).
diff --git a/libs/remix-ui/static-analyser/src/index.ts b/libs/remix-ui/static-analyser/src/index.ts
new file mode 100644
index 0000000000..86a00ccd14
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/index.ts
@@ -0,0 +1 @@
+export * from './lib/remix-ui-static-analyser'
diff --git a/libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx b/libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
new file mode 100644
index 0000000000..5cd7e9392b
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
@@ -0,0 +1,23 @@
+import React from 'react' //eslint-disable-line
+
+interface StaticAnalyserButtonProps {
+ onClick: (event) => void
+ buttonText: string,
+ disabled?: boolean
+}
+
+const StaticAnalyserButton = ({
+ onClick,
+ buttonText,
+ disabled
+}: StaticAnalyserButtonProps) => {
+ return (
+
+
+
+ )
+}
+
+export default StaticAnalyserButton
diff --git a/libs/remix-ui/static-analyser/src/lib/Checkbox/StaticAnalyserCheckedBox.tsx b/libs/remix-ui/static-analyser/src/lib/Checkbox/StaticAnalyserCheckedBox.tsx
new file mode 100644
index 0000000000..2326a39122
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/Checkbox/StaticAnalyserCheckedBox.tsx
@@ -0,0 +1,45 @@
+import React from 'react' //eslint-disable-line
+
+interface StaticAnalyserCheckBoxProps {
+ onClick?: (event) => void
+ onChange?: (event) => void
+ label?: string
+ inputType?: string
+ name?: string
+ checked?: boolean
+ id?: string
+ itemName?: string
+ categoryId?: string
+}
+
+const StaticAnalyserCheckedBox = ({
+ id,
+ label,
+ onClick,
+ inputType,
+ name,
+ checked,
+ onChange,
+ itemName,
+ categoryId
+}: StaticAnalyserCheckBoxProps) => {
+ return (
+
+
+
+
+ )
+}
+
+export default StaticAnalyserCheckedBox
diff --git a/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx b/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
new file mode 100644
index 0000000000..7b7a01f37d
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
@@ -0,0 +1,76 @@
+import React, { useState } from 'react' //eslint-disable-line
+
+interface ErrorRendererProps {
+ message: any;
+ opt: any,
+ warningErrors: any
+}
+
+const ErrorRenderer = ({ message, opt }: ErrorRendererProps) => {
+ const [, setError] = useState(
+ {
+ row: null,
+ column: null,
+ text: null,
+ type: null,
+ errFile: null
+ }
+ )
+ const getPositionDetails = (msg: any) => {
+ const result = { } as any
+
+ // 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
+ }
+ 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()
+ if (!opt.noAnnotations && opt.errFile) {
+ setError({
+ errFile: opt.errFile,
+ row: opt.errLine,
+ column: opt.errCol,
+ text: message,
+ type: opt.type
+ })
+ }
+ const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
+ return (
+
+
+
+
+
+
+ {opt.item.name}
+ { opt.item.warning }
+ {opt.item.more
+ ? more
+ :
+ }
+ Pos: {opt.locationString}
+
+
+
+ )
+}
+
+export default ErrorRenderer
diff --git a/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
new file mode 100644
index 0000000000..1674d66d84
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
@@ -0,0 +1,381 @@
+import React, { useEffect, useState } from 'react'
+import CheckBox from './Checkbox/StaticAnalyserCheckedBox' // eslint-disable-line
+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 ErrorRenderer from './ErrorRenderer' // eslint-disable-line
+const StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
+const utils = remixLib.util
+
+/* eslint-disable-next-line */
+export interface RemixUiStaticAnalyserProps {
+ renderStaticAnalysis: any
+ staticanalysis: any
+ analysisRunner: any,
+ lastCompilationResult: any,
+ lastCompilationSource: any,
+ registry: any,
+ event: any,
+ analysisModule: any
+ _deps: 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 [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
+
+ const warningContainer = React.useRef(null)
+ const [runButtonState, setRunButtonState] = useState(true)
+ const [autoRun, setAutoRun] = useState(true)
+ const [result, setResult] = useState({
+ lastCompilationResult: null,
+ lastCompilationSource: null,
+ currentFile: 'No file compiled'
+ })
+ const [, setModuleNameResult] = useState(null)
+ const [, setWarning] = useState({
+ msg: '',
+ options: {},
+ hasWarning: false,
+ warningErrors: []
+ })
+ const [warningState, setWarningState] = useState([])
+
+ useEffect(() => {
+ if (autoRun) {
+ const setCompilationResult = async (data, source, file) => {
+ await setResult({ lastCompilationResult: data, lastCompilationSource: source, currentFile: file })
+ }
+
+ if (props.analysisModule) {
+ console.log({ autoRun })
+ props.analysisModule.on(
+ 'solidity',
+ 'compilationFinished',
+ (file, source, languageVersion, data) => {
+ if (languageVersion.indexOf('soljson') !== 0) return
+ setCompilationResult(data, source, file)
+ run(data, source, file)
+ }
+ )
+ }
+ }
+ return () => { }
+ }, [autoRun])
+
+ const run = (lastCompilationResult, lastCompilationSource, currentFile) => {
+ // const highlightLocation = async (location, fileName) => {
+ // await props.analysisModule.call('editor', 'discardHighlight')
+ // await props.analysisModule.call('editor', 'highlight', location, fileName)
+ // }
+ setResult({ lastCompilationResult, lastCompilationSource, currentFile })
+ if (lastCompilationResult && categoryIndex.length) {
+ setRunButtonState(false)
+ 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
+ }
+ })
+ })
+ setModuleNameResult(moduleName)
+ 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) {
+ var split = item.location.split(':')
+ var 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 = `
+
+ ${result.name}
+ ${item.warning}
+ ${item.more
+ ? `more`
+ : ' '
+ }
+ Pos: ${locationString}
+ `
+ const options = {
+ type: 'warning',
+ useSpan: true,
+ errFile: fileName,
+ errLine: row,
+ errCol: column,
+ item: item,
+ name: result.name,
+ locationString,
+ more: item.more
+ }
+ warningErrors.push(options)
+ setWarning({ msg, hasWarning: true, options, warningErrors: warningErrors })
+ 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)
+ })
+ props.event.trigger('staticAnaysisWarning', [warningCount])
+ } else {
+ setRunButtonState(true)
+ if (categoryIndex.length) {
+ warningContainer.current.innerText = 'No compiled AST available'
+ }
+ props.event.trigger('staticAnaysisWarning', [-1])
+ }
+ }
+
+ // const correctRunBtnDisabled = () => {
+ // if (props.lastCompilationResult && selectedCategoryIndex.length !== 0) {
+ // setRunButtonState(false)
+ // } else {
+ // setRunButtonState(true)
+ // }
+ // }
+
+ 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)
+ }
+ console.log(' auton run function')
+ }
+
+ 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 (
+
+ handleCheckSingle(event, item._index)}
+ checked={categoryIndex.includes(item._index.toString())}
+ />
+
+ )
+ }
+
+ const categorySection = (category, categoryId, i) => {
+ return (
+
+
+
+
+ {category[0].categoryDisplayName}
+
+
+
+
+ }
+ expand={true}
+ >
+
+ 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))} />
+
+
+ {category.map((item, i) => {
+ return (
+ categoryItem(categoryId, item, i)
+ )
+ })}
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+ {
+ return (value.map(x => {
+ return x._index.toString()
+ }))
+ }).flat().every(el => categoryIndex.includes(el))}
+ label="Select all"
+ onClick={() => handleCheckAllModules(groupedModules)}
+ />
+
+
+
+
+
+
+
+ {Object.keys(groupedModules).map((categoryId, i) => {
+ const category = groupedModules[categoryId]
+ return (
+ categorySection(category, categoryId, i)
+ )
+ })
+ }
+
+
+ last results for:
+
+ {result.currentFile && result.currentFile}
+
+
+
+
+ {
+ (Object.entries(warningState).map((element) => (
+ <>
+ {element[0]}
+ {element[1].map(x => (
+ x.hasWarning ? () : null
+ ))}
+ >
+ )))
+ }
+
+
+
+ )
+}
+
+export default RemixUiStaticAnalyser
diff --git a/libs/remix-ui/static-analyser/tsconfig.json b/libs/remix-ui/static-analyser/tsconfig.json
new file mode 100644
index 0000000000..6b65264565
--- /dev/null
+++ b/libs/remix-ui/static-analyser/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ }
+ ]
+}
diff --git a/libs/remix-ui/static-analyser/tsconfig.lib.json b/libs/remix-ui/static-analyser/tsconfig.lib.json
new file mode 100644
index 0000000000..b560bc4dec
--- /dev/null
+++ b/libs/remix-ui/static-analyser/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"]
+}
diff --git a/nx.json b/nx.json
index 5cf047d9cc..802b6fe374 100644
--- a/nx.json
+++ b/nx.json
@@ -95,6 +95,9 @@
},
"remix-ui-workspace": {
"tags": []
+ },
+ "remix-ui-static-analyser": {
+ "tags": []
}
}
}
diff --git a/package-lock.json b/package-lock.json
index b2c38f3b0f..b5ab19b3ef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25142,9 +25142,9 @@
}
},
"lodash": {
- "version": "4.17.19",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
- "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.15",
diff --git a/package.json b/package.json
index bd2a0e8aa6..03cd967635 100644
--- a/package.json
+++ b/package.json
@@ -159,6 +159,7 @@
"isbinaryfile": "^3.0.2",
"jquery": "^3.3.1",
"jszip": "^3.6.0",
+ "lodash": "^4.17.21",
"merge": "^1.2.0",
"npm-install-version": "^6.0.2",
"react": "16.13.1",
diff --git a/tsconfig.json b/tsconfig.json
index c2a383e9de..c90ed09031 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -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,8 @@
"@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"]
}
},
"exclude": ["node_modules", "tmp"]
diff --git a/workspace.json b/workspace.json
index 10793e13b3..9c734f96a7 100644
--- a/workspace.json
+++ b/workspace.json
@@ -725,6 +725,25 @@
}
}
}
+ },
+ "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/**/*"
+ ]
+ }
+ }
+ }
}
},
"cli": {