diff --git a/.circleci/config.yml b/.circleci/config.yml index 68067dc539..e308cf5191 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,17 +8,19 @@ parameters: type: boolean default: false orbs: - browser-tools: circleci/browser-tools@1.3.0 + browser-tools: circleci/browser-tools@1.4.0 + node: circleci/node@5.0.2 jobs: build: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" @@ -26,11 +28,13 @@ jobs: working_directory: ~/remix-project steps: - checkout - + - node/install: + install-yarn: true + node-version: "v14.17.6" - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn - save_cache: key: v1-deps-{{ checksum "yarn.lock" }} paths: @@ -38,22 +42,23 @@ jobs: - run: yarn run downloadsolc_assets - run: npx nx build remix-ide - run: npx nx build remix-ide-e2e-src-local-plugin - + - run: yarn run build:libs - run: mkdir persist && zip -r persist/dist.zip dist - persist_to_workspace: root: . paths: - - 'persist' + - "persist" lint: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" @@ -62,22 +67,26 @@ jobs: parallelism: 35 steps: - checkout + - node/install: + install-yarn: true + node-version: "v14.17.6" - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn - run: name: Remix Libs Linting command: ./apps/remix-ide/ci/lint.sh remix-libs: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" @@ -86,52 +95,64 @@ jobs: steps: - checkout + - node/install: + install-yarn: true + node-version: "v14.17.6" - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install - - run: cd dist/libs/remix-tests && yarn install + - run: yarn + - run: cd dist/libs/remix-tests && yarn - run: yarn run test:libs - + remix-ide-chrome: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR: "Circle CI" working_directory: ~/remix-project - parallelism: 90 + parallelism: 95 steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - browser-tools/install-chrome - browser-tools/install-chromedriver - run: command: | - google-chrome --version - chromedriver --version - java -jar /usr/local/bin/selenium.jar --version + google-chrome --version + chromedriver --version name: Check install - checkout - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn + - run: + name: Java + command: sudo apt update && sudo apt install default-jre + - run: + name: Install Selenium + command: yarn run selenium-install - run: - name: Start Selenium - command: java -jar /usr/local/bin/selenium.jar + name: Run Selenium + command: yarn run selenium background: true - run: ./apps/remix-ide/ci/browser_test.sh chrome - store_test_results: @@ -142,39 +163,48 @@ jobs: flaky-chrome: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR: "Circle CI" working_directory: ~/remix-project - parallelism: 80 + parallelism: 95 steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - browser-tools/install-chrome - browser-tools/install-chromedriver - run: command: | - google-chrome --version - chromedriver --version - java -jar /usr/local/bin/selenium.jar --version + google-chrome --version + chromedriver --version name: Check install - checkout - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn + - run: + name: Java + command: sudo apt update && sudo apt install default-jre + - run: + name: Install Selenium + command: yarn run selenium-install - run: - name: Start Selenium - command: java -jar /usr/local/bin/selenium.jar + name: Run Selenium + command: yarn run selenium background: true - run: ./apps/remix-ide/ci/flaky.sh chrome - store_test_results: @@ -185,39 +215,49 @@ jobs: remix-ide-firefox: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR: "Circle CI" working_directory: ~/remix-project - parallelism: 90 + parallelism: 95 steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - browser-tools/install-firefox - browser-tools/install-geckodriver - run: command: | - firefox --version - geckodriver --version - java -jar /usr/local/bin/selenium.jar --version + firefox --version + geckodriver --version + name: Check install - checkout - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn + - run: + name: Java + command: sudo apt update && sudo apt install default-jre - run: - name: Start Selenium - command: java -jar /usr/local/bin/selenium.jar + name: Install Selenium + command: yarn run selenium-install + - run: + name: Run Selenium + command: yarn run selenium background: true - run: ./apps/remix-ide/ci/browser_test.sh firefox - store_test_results: @@ -227,39 +267,49 @@ jobs: flaky-firefox: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR: "Circle CI" working_directory: ~/remix-project - parallelism: 80 + parallelism: 95 steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - browser-tools/install-firefox - browser-tools/install-geckodriver - run: command: | - firefox --version - geckodriver --version - java -jar /usr/local/bin/selenium.jar --version + firefox --version + geckodriver --version + name: Check install - checkout - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn + - run: + name: Java + command: sudo apt update && sudo apt install default-jre + - run: + name: Install Selenium + command: yarn run selenium-install - run: - name: Start Selenium - command: java -jar /usr/local/bin/selenium.jar + name: Run Selenium + command: yarn run selenium background: true - run: ./apps/remix-ide/ci/flaky.sh firefox - store_test_results: @@ -270,12 +320,13 @@ jobs: remix-ide-vyper-plugin: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" @@ -283,26 +334,34 @@ jobs: working_directory: ~/remix-project parallelism: 10 steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - browser-tools/install-chrome - browser-tools/install-chromedriver - run: command: | - google-chrome --version - chromedriver --version - java -jar /usr/local/bin/selenium.jar --version + google-chrome --version + chromedriver --version + name: Check install - checkout - - checkout - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn + - run: + name: Java + command: sudo apt update && sudo apt install default-jre + - run: + name: Install Selenium + command: yarn run selenium-install - run: - name: Start Selenium - command: java -jar /usr/local/bin/selenium.jar + name: Run Selenium + command: yarn run selenium background: true - run: npx nx build vyper - run: ./apps/remix-ide/ci/browser_tests_vyper_plugin.sh @@ -314,12 +373,13 @@ jobs: remix-ide-etherscan-plugin: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" @@ -327,26 +387,34 @@ jobs: working_directory: ~/remix-project parallelism: 10 steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - browser-tools/install-chrome - browser-tools/install-chromedriver - run: command: | - google-chrome --version - chromedriver --version - java -jar /usr/local/bin/selenium.jar --version + google-chrome --version + chromedriver --version + name: Check install - checkout - - checkout - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn + - run: + name: Java + command: sudo apt update && sudo apt install default-jre + - run: + name: Install Selenium + command: yarn run selenium-install - run: - name: Start Selenium - command: java -jar /usr/local/bin/selenium.jar + name: Run Selenium + command: yarn run selenium background: true - run: npx nx build etherscan - run: ./apps/remix-ide/ci/browser_tests_etherscan_plugin.sh @@ -354,16 +422,17 @@ jobs: path: ./reports/tests - store_artifacts: path: ./reports/screenshots - + remix-ide-plugin-api: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" @@ -371,26 +440,34 @@ jobs: working_directory: ~/remix-project parallelism: 10 steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - browser-tools/install-chrome - browser-tools/install-chromedriver - run: command: | - google-chrome --version - chromedriver --version - java -jar /usr/local/bin/selenium.jar --version + google-chrome --version + chromedriver --version + name: Check install - checkout - - checkout - attach_workspace: - at: . + at: . - run: unzip ./persist/dist.zip - restore_cache: keys: - v1-deps-{{ checksum "yarn.lock" }} - - run: yarn install + - run: yarn + - run: + name: Java + command: sudo apt update && sudo apt install default-jre - run: - name: Start Selenium - command: java -jar /usr/local/bin/selenium.jar + name: Install Selenium + command: yarn run selenium-install + - run: + name: Run Selenium + command: yarn run selenium background: true - run: ./apps/remix-ide/ci/browser_tests_plugin_api.sh - store_test_results: @@ -401,12 +478,13 @@ jobs: deploy-remix-live: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - resource_class: xlarge + resource_class: + xlarge # - image: circleci/mongo:3.4.4 environment: - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" @@ -415,26 +493,29 @@ jobs: working_directory: ~/remix-project steps: + - node/install: + install-yarn: true + node-version: "v14.17.6" - checkout - - run: yarn install + - run: yarn - run: yarn run downloadsolc_assets - run: yarn run build:production - - run: + - run: name: Deploy command: | if [ "${CIRCLE_BRANCH}" == "remix_live" ]; then ./apps/remix-ide/ci/deploy_from_travis_remix-live.sh; fi - deploy-remix-alpha: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images - resource_class: xlarge + resource_class: + xlarge # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/mongo:3.4.4 environment: @@ -445,10 +526,10 @@ jobs: steps: - checkout - - run: yarn install + - run: yarn - run: yarn run downloadsolc_assets - run: yarn run build:production - - run: + - run: name: Deploy command: | if [ "${CIRCLE_BRANCH}" == "master" ]; then @@ -458,11 +539,12 @@ jobs: deploy-remix-beta: docker: # specify the version you desire here - - image: cimg/node:14.17.6-browsers + - image: cimg/base:current # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images - resource_class: xlarge + resource_class: + xlarge # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/mongo:3.4.4 environment: @@ -473,11 +555,14 @@ jobs: steps: - checkout - - run: yarn install + - node/install: + install-yarn: true + node-version: "v14.17.6" + - run: yarn - run: yarn run build:libs - run: yarn run downloadsolc_assets - run: yarn run build:production - - run: + - run: name: Deploy command: | if [ "${CIRCLE_BRANCH}" == "remix_beta" ]; then @@ -520,7 +605,7 @@ workflows: - remix-ide-firefox: requires: - build - - deploy-remix-live: + - deploy-remix-live: requires: - lint - remix-libs @@ -532,7 +617,7 @@ workflows: filters: branches: only: remix_live - - deploy-remix-alpha: + - deploy-remix-alpha: requires: - lint - remix-libs @@ -544,7 +629,7 @@ workflows: filters: branches: only: master - - deploy-remix-beta: + - deploy-remix-beta: requires: - lint - remix-libs @@ -556,4 +641,3 @@ workflows: filters: branches: only: remix_beta - diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 33eb1fed19..a8a2bc821d 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -33,6 +33,7 @@ import { ExternalHttpProvider } from './app/tabs/external-http-provider' import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider' import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider' import { FileDecorator } from './app/plugins/file-decorator' +import { CodeFormat } from './app/plugins/code-format' const isElectron = require('is-electron') @@ -63,7 +64,7 @@ const Terminal = require('./app/panels/terminal') const { TabProxy } = require('./app/panels/tab-proxy.js') class AppComponent { - constructor () { + constructor() { this.appManager = new RemixAppManager({}) this.queryParams = new QueryParams() this._components = {} @@ -100,7 +101,7 @@ class AppComponent { }) } - async run () { + async run() { // APP_MANAGER const appManager = this.appManager const pluginLoader = this.appManager.pluginLoader @@ -161,6 +162,9 @@ class AppComponent { // ------- FILE DECORATOR PLUGIN ------------------ const fileDecorator = new FileDecorator() + // ------- CODE FORMAT PLUGIN ------------------ + const codeFormat = new CodeFormat() + //----- search const search = new SearchPlugin() @@ -213,7 +217,7 @@ class AppComponent { } } ) - + const codeParser = new CodeParser(new AstWalker()) @@ -221,7 +225,7 @@ class AppComponent { const configPlugin = new ConfigPlugin() this.layout = new Layout() - + const permissionHandler = new PermissionHandlerPlugin() this.engine.register([ @@ -241,6 +245,7 @@ class AppComponent { offsetToLineColumnConverter, codeParser, fileDecorator, + codeFormat, terminal, web3Provider, compileAndRun, @@ -351,10 +356,10 @@ class AppComponent { } } - async activate () { + async activate() { const queryParams = new QueryParams() const params = queryParams.get() - + try { this.engine.register(await this.appManager.registeredPlugins()) } catch (e) { @@ -368,9 +373,9 @@ class AppComponent { await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await this.appManager.activatePlugin(['home']) await this.appManager.activatePlugin(['settings', 'config']) - await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) + await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'codeFormatter', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin(['settings']) - await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder']) + await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder']) this.appManager.on( 'filePanel', diff --git a/apps/remix-ide/src/app/plugins/code-format.ts b/apps/remix-ide/src/app/plugins/code-format.ts new file mode 100644 index 0000000000..0a01e08769 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/code-format.ts @@ -0,0 +1,82 @@ +'use strict' +import { Plugin } from '@remixproject/engine' +import prettier from 'prettier/standalone' +import { Options } from 'prettier'; +import sol from './code-format/index' +import * as ts from 'prettier/parser-typescript' +import * as babel from 'prettier/parser-babel' +import * as espree from 'prettier/parser-espree' +import path from 'path' + +const profile = { + name: 'codeFormatter', + desciption: 'prettier plugin for Remix', + methods: ['format'], + events: [''], + version: '0.0.1' +} + +export class CodeFormat extends Plugin { + + constructor() { + super(profile) + } + + async format(file: string) { + + try { + const content = await this.call('fileManager', 'readFile', file) + if (!content) return + let parserName = '' + let options: Options = { + } + switch (path.extname(file)) { + case '.sol': + parserName = 'solidity-parse' + break + case '.ts': + parserName = 'typescript' + options = { + ...options, + trailingComma: 'all', + semi: false, + singleQuote: true, + quoteProps: 'as-needed', + bracketSpacing: true, + arrowParens: 'always', + } + break + case '.js': + parserName = "espree" + options = { + ...options, + semi: false, + singleQuote: true, + } + break + case '.json': + parserName = 'json' + break + } + const result = prettier.format(content, { + plugins: [sol as any, ts, babel, espree], + parser: parserName, + ...options + }) + await this.call('fileManager', 'writeFile', file, result) + } catch (e) { + // do nothing + } + } + +} + +function getRange(index, node) { + if (node.range) { + return node.range[index]; + } + if (node.expression && node.expression.range) { + return node.expression.range[index]; + } + return null; +} diff --git a/apps/remix-ide/src/app/plugins/code-format/index.ts b/apps/remix-ide/src/app/plugins/code-format/index.ts new file mode 100644 index 0000000000..777009974c --- /dev/null +++ b/apps/remix-ide/src/app/plugins/code-format/index.ts @@ -0,0 +1,61 @@ +import { handleComments, printComment } from 'prettier-plugin-solidity/src/comments'; +import massageAstNode from 'prettier-plugin-solidity/src/clean.js'; +import options from 'prettier-plugin-solidity/src/options.js'; +import print from 'prettier-plugin-solidity/src/printer.js'; +import loc from 'prettier-plugin-solidity/src/loc.js'; +import { parse } from './parser' + +// https://prettier.io/docs/en/plugins.html#languages +// https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json +const languages = [ + { + linguistLanguageId: 237469032, + name: 'Solidity', + type: 'programming', + color: '#AA6746', + aceMode: 'text', + tmScope: 'source.solidity', + extensions: ['.sol'], + parsers: ['solidity-parse'], + vscodeLanguageIds: ['solidity'] + } +]; + +// https://prettier.io/docs/en/plugins.html#parsers +const parser = { astFormat: 'solidity-ast', parse, ...loc }; +const parsers = { + 'solidity-parse': parser +}; + +const canAttachComment = (node) => + node.type && node.type !== 'BlockComment' && node.type !== 'LineComment'; + +// https://prettier.io/docs/en/plugins.html#printers +const printers = { + 'solidity-ast': { + canAttachComment, + handleComments: { + ownLine: handleComments.handleOwnLineComment, + endOfLine: handleComments.handleEndOfLineComment, + remaining: handleComments.handleRemainingComment + }, + isBlockComment: handleComments.isBlockComment, + massageAstNode, + print, + printComment + } +}; + +// https://prettier.io/docs/en/plugins.html#defaultoptions +const defaultOptions = { + bracketSpacing: false, + tabWidth: 4 +}; + +export default { + languages, + parsers, + printers, + options, + defaultOptions +}; diff --git a/apps/remix-ide/src/app/plugins/code-format/parser.ts b/apps/remix-ide/src/app/plugins/code-format/parser.ts new file mode 100644 index 0000000000..15ef38f871 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/code-format/parser.ts @@ -0,0 +1,197 @@ +// https://prettier.io/docs/en/plugins.html#parsers +import extractComments from 'solidity-comments-extractor'; +// use the parser already included in the app +const parser = (window as any).SolidityParser +import semver from 'semver'; + +const tryHug = (node, operators) => { + if (node.type === 'BinaryOperation' && operators.includes(node.operator)) + return { + type: 'TupleExpression', + components: [node], + isArray: false + }; + return node; +}; + +export function parse(text, _parsers, options) { + const compiler = semver.coerce(options.compiler); + const parsed = parser.parse(text, { loc: true, range: true }); + parsed.comments = extractComments(text) + + parser.visit(parsed, { + PragmaDirective(ctx) { + // if the pragma is not for solidity we leave. + if (ctx.name !== 'solidity') return; + // if the compiler option has not been provided we leave. + if (!compiler) return; + // we make a check against each pragma directive in the document. + if (!semver.satisfies(compiler, ctx.value)) { + // @TODO: investigate the best way to warn that would apply to + // different editors. + // eslint-disable-next-line no-console + console.warn( + `[prettier-solidity] The compiler option is set to '${options.compiler}', which does not satisfy 'pragma solidity ${ctx.value}'.` + ); + } + }, + ModifierDefinition(ctx) { + if (!ctx.parameters) { + ctx.parameters = []; + } + }, + FunctionDefinition(ctx) { + if (!ctx.isConstructor) { + ctx.modifiers.forEach((modifier) => { + if (modifier.arguments && modifier.arguments.length === 0) { + // eslint-disable-next-line no-param-reassign + modifier.arguments = null; + } + }); + } + }, + ForStatement(ctx) { + if (ctx.initExpression) { + ctx.initExpression.omitSemicolon = true; + } + ctx.loopExpression.omitSemicolon = true; + }, + HexLiteral(ctx) { + ctx.value = options.singleQuote + ? `hex'${ctx.value.slice(4, -1)}'` + : `hex"${ctx.value.slice(4, -1)}"`; + }, + ElementaryTypeName(ctx) { + // if the compiler is below 0.8.0 we will recognize the type 'byte' as an + // alias of 'bytes1'. Otherwise we will ignore this and enforce always + // 'bytes1'. + const pre080 = compiler && semver.satisfies(compiler, '<0.8.0'); + if (!pre080 && ctx.name === 'byte') ctx.name = 'bytes1'; + + if (options.explicitTypes === 'always') { + if (ctx.name === 'uint') ctx.name = 'uint256'; + if (ctx.name === 'int') ctx.name = 'int256'; + if (pre080 && ctx.name === 'byte') ctx.name = 'bytes1'; + } else if (options.explicitTypes === 'never') { + if (ctx.name === 'uint256') ctx.name = 'uint'; + if (ctx.name === 'int256') ctx.name = 'int'; + if (pre080 && ctx.name === 'bytes1') ctx.name = 'byte'; + } + }, + BinaryOperation(ctx) { + switch (ctx.operator) { + case '+': + case '-': + ctx.left = tryHug(ctx.left, ['%']); + ctx.right = tryHug(ctx.right, ['%']); + break; + case '*': + ctx.left = tryHug(ctx.left, ['/', '%']); + break; + case '/': + ctx.left = tryHug(ctx.left, ['*', '%']); + break; + case '%': + ctx.left = tryHug(ctx.left, ['*', '/', '%']); + break; + case '**': + // If the compiler has not been given as an option using we leave a**b**c. + if (!compiler) break; + + if (semver.satisfies(compiler, '<0.8.0')) { + // If the compiler is less than 0.8.0 then a**b**c is formatted as + // (a**b)**c. + ctx.left = tryHug(ctx.left, ['**']); + break; + } + if ( + ctx.left.type === 'BinaryOperation' && + ctx.left.operator === '**' + ) { + // the parser still organizes the a**b**c as (a**b)**c so we need + // to restructure it. + ctx.right = { + type: 'TupleExpression', + components: [ + { + type: 'BinaryOperation', + operator: '**', + left: ctx.left.right, + right: ctx.right + } + ], + isArray: false + }; + ctx.left = ctx.left.left; + } + break; + case '<<': + case '>>': + ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']); + ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']); + break; + case '&': + ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']); + ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']); + break; + case '|': + ctx.left = tryHug(ctx.left, [ + '+', + '-', + '*', + '/', + '**', + '<<', + '>>', + '&', + '^' + ]); + ctx.right = tryHug(ctx.right, [ + '+', + '-', + '*', + '/', + '**', + '<<', + '>>', + '&', + '^' + ]); + break; + case '^': + ctx.left = tryHug(ctx.left, [ + '+', + '-', + '*', + '/', + '**', + '<<', + '>>', + '&' + ]); + ctx.right = tryHug(ctx.right, [ + '+', + '-', + '*', + '/', + '**', + '<<', + '>>', + '&' + ]); + break; + case '||': + ctx.left = tryHug(ctx.left, ['&&']); + ctx.right = tryHug(ctx.right, ['&&']); + break; + case '&&': + default: + break; + } + } + }); + + return parsed; +} + + diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 0f97707ce6..76fbaae663 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -11,7 +11,7 @@ const requiredModules = [ // services + layout views + system views 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider', - 'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser'] + 'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter'] // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) const dependentModules = ['hardhat', 'truffle', 'slither'] diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index f0099db93e..e1ff7ceb9f 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -122,9 +122,11 @@ export const EditorUI = (props: EditorUIProps) => { \t\t\t\t\t\t\t|_| \\_\\ |_____| |_| |_| |___| /_/\\_\\ |___| |____/ |_____|\n\n \t\t\t\t\t\t\tKeyboard Shortcuts:\n \t\t\t\t\t\t\t\tCTRL + S: Compile the current contract\n - \t\t\t\t\t\t\t\tCtrl + Shift + F : Open the File Explorer\n - \t\t\t\t\t\t\t\tCtrl + Shift + A : Open the Plugin Manager\n - \t\t\t\t\t\t\t\tCTRL + SHIFT + S: Compile the current contract & Run an associated script\n\n + \t\t\t\t\t\t\t\tCTRL + Shift + F : Open the File Explorer\n + \t\t\t\t\t\t\t\tCTRL + Shift + A : Open the Plugin Manager\n + \t\t\t\t\t\t\t\tCTRL + SHIFT + S: Compile the current contract & Run an associated script\n + \t\t\t\t\t\t\tEditor Keyboard Shortcuts:\n + \t\t\t\t\t\t\t\tCTRL + Alt + F : Format the code in the current file\n \t\t\t\t\t\t\tImportant Links:\n \t\t\t\t\t\t\t\tOfficial website about the Remix Project: https://remix-project.org/\n \t\t\t\t\t\t\t\tOfficial documentation: https://remix-ide.readthedocs.io/en/latest/\n @@ -568,6 +570,21 @@ export const EditorUI = (props: EditorUIProps) => { ], run: () => { editor.updateOptions({ fontSize: editor.getOption(43).fontSize - 1 }) }, } + const formatAction = { + id: "autoFormat", + label: "Format Code", + contextMenuOrder: 0, // choose the order + contextMenuGroupId: "formatting", // create a new grouping + keybindings: [ + // eslint-disable-next-line no-bitwise + monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyF, + ], + run: async () => { + const file = await props.plugin.call('fileManager', 'getCurrentFile') + await props.plugin.call('codeFormatter', 'format', file) + }, + } + editor.addAction(formatAction) editor.addAction(zoomOutAction) editor.addAction(zoominAction) const editorService = editor._codeEditorService; diff --git a/package.json b/package.json index 8399c040bc..cb6a863b2b 100644 --- a/package.json +++ b/package.json @@ -197,6 +197,8 @@ "merge": "^2.1.1", "monaco-editor": "^0.30.1", "npm-install-version": "^6.0.2", + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-beta.24", "raw-loader": "^4.0.2", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.0", @@ -322,7 +324,7 @@ "onchange": "^3.2.1", "request": "^2.83.0", "rimraf": "^2.6.1", - "selenium-standalone": "^8.0.4", + "selenium-standalone": "^8.2.0", "semver": "^6.3.0", "solc": "0.7.4", "tap-spec": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 3809c46cb6..498e79f2a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4103,6 +4103,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@solidity-parser/parser@^0.14.3": + version "0.14.3" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.3.tgz#0d627427b35a40d8521aaa933cc3df7d07bfa36f" + integrity sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw== + dependencies: + antlr4ts "^0.5.0-alpha.4" + "@svgr/babel-plugin-add-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" @@ -5339,6 +5346,11 @@ ansistyles@~0.1.3: resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" integrity sha512-6QWEyvMgIXX0eO972y7YPBLSBsq7UWKFAoNNTLGaOJ9bstcEL9sCbcjf96dVfNDdUsRoGOK82vWFJlKApXds7g== +antlr4ts@^0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -8167,10 +8179,10 @@ commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.0.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c" + integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== commondir@^1.0.1: version "1.0.1" @@ -9740,6 +9752,11 @@ emittery@^0.8.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== +emoji-regex@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.1.0.tgz#d50e383743c0f7a5945c47087295afc112e3cf66" + integrity sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -9989,7 +10006,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@4.0.0: +escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== @@ -19118,6 +19135,23 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= +prettier-plugin-solidity@^1.0.0-beta.24: + version "1.0.0-beta.24" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.24.tgz#67573ca87098c14f7ccff3639ddd8a4cab2a87eb" + integrity sha512-6JlV5BBTWzmDSq4kZ9PTXc3eLOX7DF5HpbqmmaF+kloyUwOZbJ12hIYsUaZh2fVgZdV2t0vWcvY6qhILhlzgqg== + dependencies: + "@solidity-parser/parser" "^0.14.3" + emoji-regex "^10.1.0" + escape-string-regexp "^4.0.0" + semver "^7.3.7" + solidity-comments-extractor "^0.0.7" + string-width "^4.2.3" + +prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" @@ -20850,12 +20884,12 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selenium-standalone@^8.0.4: - version "8.0.8" - resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-8.0.8.tgz#15f8202d2fed2bcc9264c10f2a8da4f71f85b377" - integrity sha512-2NCHoK12dbLbtXPIdMe1ljiLAMflJDBezPhjJBwmnYz5/yGjCPv2R9ojHO6W1ctD9DOlqyi3AxoAvlT2nmB7Fw== +selenium-standalone@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-8.2.0.tgz#378b9740fe84953083fa9c0b1d8dcbfc3bd8508c" + integrity sha512-gRFJm2A91sL0/4PavIsfTVNjyqNjk+zbdJg/zAYgTMjuoWJv+BlYJh+1UbEtyjt4YvZACYif1DFAzFjQapqiOA== dependencies: - commander "^8.3.0" + commander "^9.0.0" cross-spawn "^7.0.3" debug "^4.3.1" fs-extra "^10.0.0" @@ -20951,6 +20985,13 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semve resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -21370,6 +21411,11 @@ solc@0.7.4: semver "^5.5.0" tmp "0.0.33" +solidity-comments-extractor@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" + integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== + sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" @@ -21798,7 +21844,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==