diff --git a/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts b/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts index a68a4a24ce..504148b266 100644 --- a/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts +++ b/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts @@ -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', diff --git a/apps/remix-ide/src/app/tabs/analysis-tab.js b/apps/remix-ide/src/app/tabs/analysis-tab.js index 4406ba875e..b3bba2705c 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,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`
${this.staticanalysis.render()}
` + renderComponent () { + ReactDOM.render( + , + 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' }) + } + }) + } + ) } } diff --git a/apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js b/apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js deleted file mode 100644 index 668adab89e..0000000000 --- a/apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js +++ /dev/null @@ -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`` - const view = yo` -
-
-
-
- - -
-
- - -
- ${this.runBtn} -
-
-
- ${this.modulesView} -
-
- last results for: - ${this.currentFile} -
-
-
- ` - - 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(` -
- ${moduleName} -
- `) - } - - 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` - - ${result.name} - ${item.warning} - ${item.more ? yo`more` : yo``} - Pos: ${locationString} - ` - 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` -
- - -
- ` - }) - return yo` -
- this.handleCollapse(e)}"/> - -
- ${entriesDom} -
-
- ` - }) - // collaps first module - moduleEntries[0].getElementsByTagName('input')[0].checked = true - moduleEntries[0].getElementsByTagName('i')[0].hidden = true - return yo` -
- ${moduleEntries} -
` -} - -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 - }) -} diff --git a/apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js b/apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js deleted file mode 100644 index bd277a03aa..0000000000 --- a/apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js +++ /dev/null @@ -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 diff --git a/libs/remix-ui/checkbox/.babelrc b/libs/remix-ui/checkbox/.babelrc new file mode 100644 index 0000000000..09d67939cc --- /dev/null +++ b/libs/remix-ui/checkbox/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@nrwl/react/babel"], + "plugins": [] +} diff --git a/libs/remix-ui/checkbox/.eslintrc b/libs/remix-ui/checkbox/.eslintrc new file mode 100644 index 0000000000..dae5c6feeb --- /dev/null +++ b/libs/remix-ui/checkbox/.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/checkbox/README.md b/libs/remix-ui/checkbox/README.md new file mode 100644 index 0000000000..56f9b617b0 --- /dev/null +++ b/libs/remix-ui/checkbox/README.md @@ -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). diff --git a/libs/remix-ui/checkbox/src/index.ts b/libs/remix-ui/checkbox/src/index.ts new file mode 100644 index 0000000000..27b694c6bd --- /dev/null +++ b/libs/remix-ui/checkbox/src/index.ts @@ -0,0 +1 @@ +export * from './lib/remix-ui-checkbox' diff --git a/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.css b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx new file mode 100644 index 0000000000..5535a05971 --- /dev/null +++ b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx @@ -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 ( +
+ + +
+ ) +} + +export default RemixUiCheckbox diff --git a/libs/remix-ui/checkbox/tsconfig.json b/libs/remix-ui/checkbox/tsconfig.json new file mode 100644 index 0000000000..6b65264565 --- /dev/null +++ b/libs/remix-ui/checkbox/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/checkbox/tsconfig.lib.json b/libs/remix-ui/checkbox/tsconfig.lib.json new file mode 100644 index 0000000000..b560bc4dec --- /dev/null +++ b/libs/remix-ui/checkbox/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/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..2a67c82cf8 --- /dev/null +++ b/libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx @@ -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 ( + + ) +} + +export default StaticAnalyserButton 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..8221d3f694 --- /dev/null +++ b/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx @@ -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 + + // 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 ( +
+
+
+ +
+ handlePointToErrorOnClick(opt.location, opt.fileName)}> + {opt.name} + { opt.item.warning } + {opt.item.more + ? more + : + } + Pos: {opt.locationString} + +
+
+ ) +} + +export default ErrorRenderer diff --git a/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts b/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts new file mode 100644 index 0000000000..4f55437cb6 --- /dev/null +++ b/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts @@ -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 } }) + } + ) + } +} diff --git a/libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts b/libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts new file mode 100644 index 0000000000..eb59ca7872 --- /dev/null +++ b/libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts @@ -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 + } +} 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..5bb91441af --- /dev/null +++ b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx @@ -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 (` + + ${name} + ${warning} + ${more + ? (more) + : ( ) + } + Pos: ${locationString} + ` + ) + } + + 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 ( +
+ handleCheckSingle(event, item._index)} + checked={categoryIndex.includes(item._index.toString())} + onChange={() => {}} + /> +
+ ) + } + + const categorySection = (category, categoryId, i) => { + return ( +
+
+ + + {category[0].categoryDisplayName} + + } + expand={false} + > +
+ 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={() => {}}/> +
+
+ {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)} + onChange={() => {}} + /> + {}} + /> +
+
+
+ {Object.keys(groupedModules).map((categoryId, i) => { + const category = groupedModules[categoryId] + return ( + categorySection(category, categoryId, i) + ) + }) + } +
+
+ last results for: + + {state.file} + +
+ { categoryIndex.length > 0 && Object.entries(warningState).length > 0 && +
+
+ { + (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..a63c015d64 100644 --- a/nx.json +++ b/nx.json @@ -95,6 +95,12 @@ }, "remix-ui-workspace": { "tags": [] + }, + "remix-ui-static-analyser": { + "tags": [] + }, + "remix-ui-checkbox": { + "tags": [] } } } diff --git a/package.json b/package.json index a7ae4d3288..0e3b262c5e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tsconfig.json b/tsconfig.json index c2a383e9de..75db5bc6ff 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,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"] diff --git a/workspace.json b/workspace.json index 10793e13b3..5d2bf912c0 100644 --- a/workspace.json +++ b/workspace.json @@ -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": {