├── .changeset ├── README.md ├── config.json └── nasty-chairs-grin.md ├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.cjs ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.config.ts ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── classes │ ├── AssemblyStatement.html │ ├── AssignOp.html │ ├── Assignment.html │ ├── Block.html │ ├── BooleanLiteral.html │ ├── BreakStatement.html │ ├── CatchClause.html │ ├── Conditional.html │ ├── ContinueStatement.html │ ├── ContractDefinition.html │ ├── DataLocation.html │ ├── DoWhileStatement.html │ ├── ElementaryTypeName.html │ ├── EmitStatement.html │ ├── EnumDefinition.html │ ├── ErrorDefinition.html │ ├── EventDefinition.html │ ├── ExpressionStatement.html │ ├── ForStatement.html │ ├── FunctionCall.html │ ├── FunctionCallOptions.html │ ├── FunctionDefinition.html │ ├── FunctionTypeName.html │ ├── HexStringLiteral.html │ ├── Identifier.html │ ├── IdentifierPath.html │ ├── IfStatement.html │ ├── ImportAliases.html │ ├── ImportDirective.html │ ├── IndexAccess.html │ ├── IndexRangeAccess.html │ ├── InheritanceSpecifier.html │ ├── MappingKeyType.html │ ├── MappingType.html │ ├── MemberAccess.html │ ├── MetaType.html │ ├── ModifierDefinition.html │ ├── ModifierInvocation.html │ ├── NamedArgument.html │ ├── NewExpr.html │ ├── NumberLiteral.html │ ├── Path.html │ ├── PayableConversion.html │ ├── PragmaDirective.html │ ├── ReturnStatement.html │ ├── RevertStatement.html │ ├── SourceUnit.html │ ├── StateMutability.html │ ├── StringLiteral.html │ ├── StructDefinition.html │ ├── StructMember.html │ ├── TryStatement.html │ ├── TypeName.html │ ├── UnaryOperation.html │ ├── UnicodeStringLiteral.html │ ├── UserDefinableOperator.html │ ├── UserDefinedValueTypeDefinition.html │ ├── UsingDirective.html │ ├── VariableDeclaration.html │ ├── VariableDeclarationStatement.html │ ├── Visibility.html │ ├── WhileStatement.html │ ├── YulAssignment.html │ ├── YulBlock.html │ ├── YulBoolean.html │ ├── YulForStatement.html │ ├── YulFunctionCall.html │ ├── YulFunctionDefinition.html │ ├── YulIfStatement.html │ ├── YulLiteral.html │ ├── YulPath.html │ ├── YulStatement.html │ ├── YulSwitchCase.html │ ├── YulSwitchStatement.html │ └── YulVariableDeclaration.html ├── index.html └── modules.html ├── grammar ├── SolidityLexer.g4 └── SolidityParser.g4 ├── package.json ├── pnpm-lock.yaml ├── src ├── antlr4 │ ├── SolidityLexer.interp │ ├── SolidityLexer.tokens │ ├── SolidityLexer.ts │ ├── SolidityParser.interp │ ├── SolidityParser.tokens │ ├── SolidityParser.ts │ ├── SolidityParserListener.ts │ ├── SolidityParserVisitor.ts │ └── index.ts ├── ast │ ├── base.ts │ ├── builder.ts │ ├── declaration │ │ ├── contract-body-element.ts │ │ ├── contract-definition.ts │ │ ├── enum-definition.ts │ │ ├── error-definition.ts │ │ ├── event-definition.ts │ │ ├── function-definition.ts │ │ ├── index.node.ts │ │ ├── index.ts │ │ ├── modifier-definition.ts │ │ ├── state-mutability.ts │ │ ├── struct-definition.ts │ │ ├── struct-member.ts │ │ ├── user-defined-value-type-definition.ts │ │ ├── variable-declaration-list.ts │ │ ├── variable-declaration-tuple.ts │ │ ├── variable-declaration.ts │ │ └── visibility.ts │ ├── expression │ │ ├── assign-op.ts │ │ ├── assignment.ts │ │ ├── binary-operation.ts │ │ ├── boolean-literal.ts │ │ ├── call-argument-list.ts │ │ ├── conditional.ts │ │ ├── expression.ts │ │ ├── function-call-options.ts │ │ ├── function-call.ts │ │ ├── hex-string-literal.ts │ │ ├── identifier.ts │ │ ├── index-access.ts │ │ ├── index-range-access.ts │ │ ├── index.node.ts │ │ ├── index.ts │ │ ├── inline-array-expression.ts │ │ ├── inline-array.ts │ │ ├── literal.ts │ │ ├── member-access.ts │ │ ├── meta-type.ts │ │ ├── named-argument.ts │ │ ├── new-expr.ts │ │ ├── number-literal.ts │ │ ├── payable-conversion.ts │ │ ├── primary-expression.ts │ │ ├── string-literal.ts │ │ ├── tuple-expression.ts │ │ ├── tuple.ts │ │ ├── unary-operation.ts │ │ ├── unicode-string-literal.ts │ │ └── user-definable-operator.ts │ ├── index.node.ts │ ├── index.ts │ ├── meta │ │ ├── data-location.ts │ │ ├── identifier-path.ts │ │ ├── import-aliases.ts │ │ ├── import-directive.ts │ │ ├── index.node.ts │ │ ├── index.ts │ │ ├── inheritance-specifier-list.ts │ │ ├── inheritance-specifier.ts │ │ ├── modifier-invocation.ts │ │ ├── override-specifier.ts │ │ ├── parameter-list.ts │ │ ├── path.ts │ │ ├── pragma-directive.ts │ │ ├── source-unit.ts │ │ ├── symbol-aliases.ts │ │ ├── using-aliases.ts │ │ └── using-directive.ts │ ├── statement │ │ ├── assembly-flags.ts │ │ ├── assembly-statement.ts │ │ ├── block.ts │ │ ├── break-statement.ts │ │ ├── catch-clause.ts │ │ ├── continue-statement.ts │ │ ├── do-while-statement.ts │ │ ├── emit-statement.ts │ │ ├── expression-statement.ts │ │ ├── for-statement.ts │ │ ├── if-statement.ts │ │ ├── index.node.ts │ │ ├── index.ts │ │ ├── return-statement.ts │ │ ├── revert-statement.ts │ │ ├── simple-statement.ts │ │ ├── statement.ts │ │ ├── try-statement.ts │ │ ├── variable-declaration-statement.ts │ │ └── while-statement.ts │ ├── type │ │ ├── elementary-type-name.ts │ │ ├── function-type-name.ts │ │ ├── index.node.ts │ │ ├── index.ts │ │ ├── mapping-key-type.ts │ │ ├── mapping-type.ts │ │ └── type-name.ts │ └── yul │ │ ├── index.node.ts │ │ ├── index.ts │ │ ├── yul-assignment.ts │ │ ├── yul-block.ts │ │ ├── yul-boolean.ts │ │ ├── yul-expression.ts │ │ ├── yul-for-statement.ts │ │ ├── yul-function-call.ts │ │ ├── yul-function-definition.ts │ │ ├── yul-if-statement.ts │ │ ├── yul-literal.ts │ │ ├── yul-path.ts │ │ ├── yul-statement.ts │ │ ├── yul-switch-case.ts │ │ ├── yul-switch-statement.ts │ │ └── yul-variable-declaration.ts ├── generator │ ├── generate.ts │ └── index.ts ├── index.ts ├── parser │ ├── error-listener.ts │ ├── index.ts │ ├── parse.ts │ └── tokenizer.ts ├── prettier │ ├── index.ts │ ├── options.ts │ ├── parser.ts │ ├── printer.ts │ └── printers │ │ ├── base.ts │ │ ├── comment.ts │ │ ├── declaration.ts │ │ ├── expression.ts │ │ ├── index.ts │ │ ├── meta.ts │ │ ├── statement.ts │ │ ├── type.ts │ │ └── yul.ts ├── tests │ ├── __files__ │ │ ├── comment.txt │ │ └── meta.txt │ ├── __snapshots__ │ │ └── prettier.test.ts.snap │ ├── declaration.test.ts │ ├── expression.test.ts │ ├── generator.test.ts │ ├── meta.test.ts │ ├── parser.test.ts │ ├── prettier.test.ts │ ├── statement.test.ts │ ├── traverse.test.ts │ ├── type.test.ts │ ├── utils.test.ts │ └── yul.test.ts ├── traverse │ ├── index.ts │ ├── selector.ts │ ├── serialize.ts │ ├── traverse.ts │ └── visit.ts └── typedoc.ts ├── tsconfig.json └── vitest.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/changelog-git", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/nasty-chairs-grin.md: -------------------------------------------------------------------------------- 1 | --- 2 | "solidity-antlr4": patch 3 | --- 4 | 5 | make call-argument-list as a ast node 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.cjs 3 | *.mjs 4 | *.md 5 | src/antlr4/* 6 | dist/* 7 | build.config.ts 8 | vitest.config.ts 9 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('@umijs/fabric/dist/eslint')], 3 | rules: { 4 | '@typescript-eslint/no-unused-vars': [1, { argsIgnorePattern: '^_' }], 5 | '@typescript-eslint/no-parameter-properties': 0, 6 | '@typescript-eslint/no-empty-interface': 0, 7 | '@typescript-eslint/consistent-type-imports': 0, 8 | '@typescript-eslint/no-useless-constructor': 0, 9 | '@typescript-eslint/no-redeclare': 0, 10 | 'import/no-extraneous-dependencies': 0, 11 | 'no-console': 0, 12 | }, 13 | // https://www.npmjs.com/package/@typescript-eslint/parser 14 | parserOptions: { 15 | project: ['tsconfig.json', 'tsconfig.*.json'], 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | - push 3 | - pull_request 4 | 5 | jobs: 6 | unit-test-and-lint: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | 13 | - name: Install nodejs 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 18 17 | 18 | - uses: pnpm/action-setup@v2 19 | name: Install pnpm 20 | with: 21 | version: 8 22 | run_install: false 23 | 24 | - name: Get pnpm store directory 25 | shell: bash 26 | run: | 27 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 28 | 29 | - uses: actions/cache@v3 30 | name: Setup pnpm cache 31 | with: 32 | path: ${{ env.STORE_PATH }} 33 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 34 | restore-keys: | 35 | ${{ runner.os }}-pnpm-store- 36 | 37 | - name: Install dependencies 38 | run: pnpm install 39 | 40 | - name: Lint ant prettier 41 | run: pnpm run lint 42 | 43 | - name: Build 44 | run: pnpm run build 45 | 46 | - name: Run test 47 | run: pnpm run test:ci 48 | 49 | - name: Upload coverage reports to Codecov 50 | uses: codecov/codecov-action@v3 51 | env: 52 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | .DS_Store 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # yarn v2 127 | .yarn/cache 128 | .yarn/unplugged 129 | .yarn/build-state.yml 130 | .yarn/install-state.gz 131 | .pnp.* 132 | 133 | grammar/.antlr/* 134 | html 135 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | src/antlr4/* 3 | dist/* 4 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@umijs/fabric').prettier, 3 | }; 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # solidity-antlr4 2 | 3 | ## 2.7.1 4 | 5 | ### Patch Changes 6 | 7 | - 763036c: change function declaration node 8 | - 70b9568: prettier plugin support comment 9 | 10 | ## 2.7.0 11 | 12 | ### Minor Changes 13 | 14 | - ee62da8: change ast node type 15 | - cbf7675: support prettier plugin 16 | 17 | ### Patch Changes 18 | 19 | - 34a30da: add codegen based on prettier 20 | 21 | ## 2.6.2 22 | 23 | ### Patch Changes 24 | 25 | - 89467d8: support order when query selector 26 | 27 | ## 2.6.1 28 | 29 | ### Patch Changes 30 | 31 | - c752710: support offset filter when query selector 32 | 33 | ## 2.6.0 34 | 35 | ### Minor Changes 36 | 37 | - 680d17c: support css like ast selector 38 | 39 | ### Patch Changes 40 | 41 | - 054ba06: alias expression statement as expression 42 | - 4aa05d2: correct symbol alias types 43 | 44 | ## 2.5.2 45 | 46 | ### Patch Changes 47 | 48 | - 1284c64: remove `index` in traverse path 49 | 50 | ## 2.5.0 51 | 52 | ### Minor Changes 53 | 54 | - ab1681a: refact ast traverse utils, make it easy to use 55 | 56 | ## 2.4.3 57 | 58 | ### Patch Changes 59 | 60 | - c993dee: re define union types 61 | 62 | ## 2.4.2 63 | 64 | ### Patch Changes 65 | 66 | - e343fc8: fix some issue in event and error definition 67 | 68 | ## 2.4.1 69 | 70 | ### Patch Changes 71 | 72 | - 0bbaf07: support matches when traverse 73 | 74 | ## 2.4.0 75 | 76 | ### Minor Changes 77 | 78 | - 45e7e93: changed callback param of traverse enter/exit 79 | 80 | ## 2.3.0 81 | 82 | ### Minor Changes 83 | 84 | - dcf10a7: reduce and combine ast nodes, use `xxxKind` to distinguish 85 | 86 | ### Patch Changes 87 | 88 | - 6e87962: support traverse/walker and refact serialize 89 | 90 | ## 2.2.1 91 | 92 | ### Patch Changes 93 | 94 | - faa6480: format dist files 95 | 96 | ## 2.2.0 97 | 98 | ### Minor Changes 99 | 100 | - 4bc5dea: use unbuild to build standard esm/cjs dists 101 | 102 | ## 2.1.3 103 | 104 | ### Patch Changes 105 | 106 | - 5e47215: change build targets to es5 107 | 108 | ## 2.1.2 109 | 110 | ### Patch Changes 111 | 112 | - abe94c4: add main to packagejson for legacy node version 113 | 114 | ## 2.1.1 115 | 116 | ### Patch Changes 117 | 118 | - patch for unit tests and npm package keywords 119 | 120 | ## 2.1.0 121 | 122 | ### Minor Changes 123 | 124 | - 90fa8b5: update readme and format file locations 125 | 126 | ## 2.0.0 127 | 128 | ### Major Changes 129 | 130 | - support syntax tree and tokenizer 131 | 132 | ## 1.1.0 133 | 134 | ### Minor Changes 135 | 136 | - 41d84b5: core ast visitors 137 | 138 | ## 1.0.1 139 | 140 | ### Patch Changes 141 | 142 | - e29c95d: clean project 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jeason 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig, MkdistBuildEntry } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | entries: [ 5 | { 6 | builder: 'mkdist', 7 | input: './src/', 8 | format: 'esm', 9 | ext: 'js', 10 | declaration: true, 11 | }, 12 | { 13 | builder: 'mkdist', 14 | input: './src/', 15 | format: 'cjs', 16 | ext: 'cjs', 17 | declaration: false, 18 | }, 19 | ], 20 | failOnWarn: false, 21 | }); 22 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #008000; 9 | --dark-hl-3: #6A9955; 10 | --light-hl-4: #AF00DB; 11 | --dark-hl-4: #C586C0; 12 | --light-hl-5: #001080; 13 | --dark-hl-5: #9CDCFE; 14 | --light-hl-6: #0000FF; 15 | --dark-hl-6: #569CD6; 16 | --light-hl-7: #0070C1; 17 | --dark-hl-7: #4FC1FF; 18 | --light-code-background: #FFFFFF; 19 | --dark-code-background: #1E1E1E; 20 | } 21 | 22 | @media (prefers-color-scheme: light) { :root { 23 | --hl-0: var(--light-hl-0); 24 | --hl-1: var(--light-hl-1); 25 | --hl-2: var(--light-hl-2); 26 | --hl-3: var(--light-hl-3); 27 | --hl-4: var(--light-hl-4); 28 | --hl-5: var(--light-hl-5); 29 | --hl-6: var(--light-hl-6); 30 | --hl-7: var(--light-hl-7); 31 | --code-background: var(--light-code-background); 32 | } } 33 | 34 | @media (prefers-color-scheme: dark) { :root { 35 | --hl-0: var(--dark-hl-0); 36 | --hl-1: var(--dark-hl-1); 37 | --hl-2: var(--dark-hl-2); 38 | --hl-3: var(--dark-hl-3); 39 | --hl-4: var(--dark-hl-4); 40 | --hl-5: var(--dark-hl-5); 41 | --hl-6: var(--dark-hl-6); 42 | --hl-7: var(--dark-hl-7); 43 | --code-background: var(--dark-code-background); 44 | } } 45 | 46 | :root[data-theme='light'] { 47 | --hl-0: var(--light-hl-0); 48 | --hl-1: var(--light-hl-1); 49 | --hl-2: var(--light-hl-2); 50 | --hl-3: var(--light-hl-3); 51 | --hl-4: var(--light-hl-4); 52 | --hl-5: var(--light-hl-5); 53 | --hl-6: var(--light-hl-6); 54 | --hl-7: var(--light-hl-7); 55 | --code-background: var(--light-code-background); 56 | } 57 | 58 | :root[data-theme='dark'] { 59 | --hl-0: var(--dark-hl-0); 60 | --hl-1: var(--dark-hl-1); 61 | --hl-2: var(--dark-hl-2); 62 | --hl-3: var(--dark-hl-3); 63 | --hl-4: var(--dark-hl-4); 64 | --hl-5: var(--dark-hl-5); 65 | --hl-6: var(--dark-hl-6); 66 | --hl-7: var(--dark-hl-7); 67 | --code-background: var(--dark-code-background); 68 | } 69 | 70 | .hl-0 { color: var(--hl-0); } 71 | .hl-1 { color: var(--hl-1); } 72 | .hl-2 { color: var(--hl-2); } 73 | .hl-3 { color: var(--hl-3); } 74 | .hl-4 { color: var(--hl-4); } 75 | .hl-5 { color: var(--hl-5); } 76 | .hl-6 { color: var(--hl-6); } 77 | .hl-7 { color: var(--hl-7); } 78 | pre, code { background: var(--code-background); } 79 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE5WYT28TMRDFv0vOFQhOqLfQgKhoadW0RQhxmO5OE6tee+X1pokQ3x1vEpT12n42tyjvzW/tsT3+8/P3zPLWzs5n867j5knulpYsN6zs7GzWkl07qZLkxO5tYHmzto10vheh6tn5u/cf/pyNeWKlbtooZq/ko5PNOGqI8FHq6iUM3v8N47SWTOpKWDYkIwBPhyTD9ALS6euIdEG2Wl9I6jsOMSMRMrSqhRVaxTo1EjMMK1TPoFOBJcczVNkFPwu1/34c6HsQcUGWrnRFcdZYhRT9fS0k6ubUgWif5N5CZne/a/kbNZFBDD2Q2AgLGufJkKP6BuXe1yHJGG0gyjdA1sY1G7J8A2RtW8OuXmiF8hWaEPOzNgA2ViGlV9XQ+guSkQU5VkspN+3wq8Owo6mEiYYg9JQQ0/N/6kC0L7xdWiPUKlmjpw5Eu6zdQIlnwSbknLQywu0QDiiDDknPYGKNRMhoWm3sXApyQRHKWM5zFsKwG5hNZMgmBshSNW/nVeUWWYRzErOMO1IrhqCRA9PWbIQlVfGy5So1/hEXol5T27pZ95X3VTzk+XoBCWKyDHdYY5PK1ljFFEuJZhwVGK3rfdZQLQk9JcRLtUnu86EHEYd6U8/Nqo+vO0+GHH4ddpII4SDA2H4YjWRB82TEiVegXN25pR09SXZHrQ2bLprSwAJ5hlYNgdoxMSDWHdveoN17YsAs13h0cpoYEGupe1Pxg5uwIeakQcLwmeve0pOQwu4iGN+AWXBDLN4NnbHH5/GpI087VJoU6aAiyr1Bl9KxCinJs0fJmeNBuaP5TevSF8+Kr2OSqHTNmQGLuSC1O9bPYZEe2qEjKY/airhcP5LsecgVmh3Yj7/kegpKhq8j0iMZMfRvwS42NWAR038ywZxEbvgV0YlUMThpiJC7t5bfWn/0Ej3CeHKGk3iK+afkog8PLvH4g5Yh4AvbxJBjwWvbxFDIQgsqastw4TXC1zOkZH06aRlC/EByFDKxuBelfVi+iuGBjGJvZ55cxMm0yfdkiEWFKu4Lyb/+Ah9rk4rLFQAA" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidity-antlr4", 3 | "description": "Solidity Lang Lexer and Parser by official ANTLR4 grammar", 4 | "version": "2.7.1", 5 | "keywords": [ 6 | "solidity", 7 | "sol", 8 | "ast", 9 | "parser", 10 | "lexer", 11 | "antlr4" 12 | ], 13 | "type": "module", 14 | "module": "./dist/index.js", 15 | "main": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts", 17 | "exports": { 18 | ".": { 19 | "import": "./dist/index.js", 20 | "require": "./dist/index.cjs", 21 | "types": "./dist/index.d.ts" 22 | }, 23 | "./ast": { 24 | "import": "./dist/ast/index.js", 25 | "require": "./dist/ast/index.cjs", 26 | "types": "./dist/ast/index.d.ts" 27 | }, 28 | "./antlr4": { 29 | "import": "./dist/antlr4/index.js", 30 | "require": "./dist/antlr4/index.cjs", 31 | "types": "./dist/antlr4/index.d.ts" 32 | }, 33 | "./parser": { 34 | "import": "./dist/parser/index.js", 35 | "require": "./dist/parser/index.cjs", 36 | "types": "./dist/parser/index.d.ts" 37 | }, 38 | "./traverse": { 39 | "import": "./dist/traverse/index.js", 40 | "require": "./dist/traverse/index.cjs", 41 | "types": "./dist/traverse/index.d.ts" 42 | }, 43 | "./prettier": { 44 | "import": "./dist/prettier/index.js", 45 | "require": "./dist/prettier/index.cjs", 46 | "types": "./dist/prettier/index.d.ts" 47 | }, 48 | "./generator": { 49 | "import": "./dist/generator/index.js", 50 | "require": "./dist/generator/index.cjs", 51 | "types": "./dist/generator/index.d.ts" 52 | } 53 | }, 54 | "files": [ 55 | "dist", 56 | "grammar", 57 | "CHANGELOG.md", 58 | "README.md" 59 | ], 60 | "sideEffects": false, 61 | "author": "jeason.eth ", 62 | "repository": "git@github.com:jeasonstudio/solidity-antlr4.git", 63 | "lint-staged": { 64 | "*.ts": "eslint --quiet --fix", 65 | "*.{ts}": "prettier --loglevel warn --write" 66 | }, 67 | "dependencies": { 68 | "antlr4ng": "^2.0.6", 69 | "lodash-es": "^4.17.21", 70 | "type-fest": "^4.9.0" 71 | }, 72 | "devDependencies": { 73 | "@changesets/changelog-git": "^0.2.0", 74 | "@changesets/cli": "^2.27.1", 75 | "@types/lodash-es": "^4.17.12", 76 | "@umijs/fabric": "^4.0.1", 77 | "@vitest/coverage-v8": "^1.2.1", 78 | "@vitest/ui": "^1.2.1", 79 | "antlr4ng-cli": "^1.0.7", 80 | "changeset": "^0.2.6", 81 | "eslint": "^8.56.0", 82 | "husky": "^8.0.3", 83 | "lint-staged": "^15.2.0", 84 | "prettier": "^3.2.4", 85 | "typedoc": "^0.25.4", 86 | "typescript": "~5.1.0", 87 | "unbuild": "^2.0.0", 88 | "vitest": "^1.2.1" 89 | }, 90 | "peerDependencies": { 91 | "prettier": "^3.0.0" 92 | }, 93 | "publishConfig": { 94 | "registry": "https://registry.npmjs.org", 95 | "access": "public" 96 | }, 97 | "scripts": { 98 | "grammar": "antlr4ng -Dlanguage=TypeScript -o src/antlr4/ -message-format vs2005 -visitor -listener -long-messages -Xexact-output-dir grammar/SolidityLexer.g4 grammar/SolidityParser.g4", 99 | "dev": "unbuild --stub", 100 | "build": "unbuild --clean", 101 | "typedoc": "typedoc --tsconfig ./tsconfig.json --skipErrorChecking src/typedoc.ts", 102 | "prepublishOnly": "npm run build", 103 | "changeset": "changeset", 104 | "release": "changeset version && changeset publish", 105 | "test": "vitest", 106 | "test:coverage": "vitest --coverage", 107 | "test:ui": "vitest --ui", 108 | "test:ci": "npm run test:coverage", 109 | "lint": "pnpm run \"/^lint:.+/\"", 110 | "lint:eslint": "eslint --quiet --fix --ext .ts .", 111 | "lint:prettier": "prettier **/*.ts --log-level warn --write", 112 | "husky:prepare": "husky install", 113 | "husky:pre-commit": "lint-staged" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/antlr4/SolidityLexer.tokens: -------------------------------------------------------------------------------- 1 | ReservedKeywords=1 2 | Abstract=2 3 | Address=3 4 | Anonymous=4 5 | As=5 6 | Assembly=6 7 | Bool=7 8 | Break=8 9 | Bytes=9 10 | Calldata=10 11 | Catch=11 12 | Constant=12 13 | Constructor=13 14 | Continue=14 15 | Contract=15 16 | Delete=16 17 | Do=17 18 | Else=18 19 | Emit=19 20 | Enum=20 21 | Error=21 22 | Event=22 23 | External=23 24 | Fallback=24 25 | False=25 26 | Fixed=26 27 | FixedBytes=27 28 | For=28 29 | From=29 30 | Function=30 31 | Global=31 32 | Hex=32 33 | If=33 34 | Immutable=34 35 | Import=35 36 | Indexed=36 37 | Interface=37 38 | Internal=38 39 | Is=39 40 | Library=40 41 | Mapping=41 42 | Memory=42 43 | Modifier=43 44 | New=44 45 | SubDenomination=45 46 | Override=46 47 | Payable=47 48 | Pragma=48 49 | Private=49 50 | Public=50 51 | Pure=51 52 | Receive=52 53 | Return=53 54 | Returns=54 55 | Revert=55 56 | SignedIntegerType=56 57 | Storage=57 58 | String=58 59 | Struct=59 60 | True=60 61 | Try=61 62 | Type=62 63 | Ufixed=63 64 | Unchecked=64 65 | Unicode=65 66 | UnsignedIntegerType=66 67 | Using=67 68 | View=68 69 | Virtual=69 70 | While=70 71 | LParen=71 72 | RParen=72 73 | LBrack=73 74 | RBrack=74 75 | LBrace=75 76 | RBrace=76 77 | Colon=77 78 | Semicolon=78 79 | Period=79 80 | Conditional=80 81 | DoubleArrow=81 82 | RightArrow=82 83 | Assign=83 84 | AssignBitOr=84 85 | AssignBitXor=85 86 | AssignBitAnd=86 87 | AssignShl=87 88 | AssignSar=88 89 | AssignShr=89 90 | AssignAdd=90 91 | AssignSub=91 92 | AssignMul=92 93 | AssignDiv=93 94 | AssignMod=94 95 | Comma=95 96 | Or=96 97 | And=97 98 | BitOr=98 99 | BitXor=99 100 | BitAnd=100 101 | Shl=101 102 | Sar=102 103 | Shr=103 104 | Add=104 105 | Sub=105 106 | Mul=106 107 | Div=107 108 | Mod=108 109 | Exp=109 110 | Equal=110 111 | NotEqual=111 112 | LessThan=112 113 | GreaterThan=113 114 | LessThanOrEqual=114 115 | GreaterThanOrEqual=115 116 | Not=116 117 | BitNot=117 118 | Inc=118 119 | Dec=119 120 | DoubleQuote=120 121 | SingleQuote=121 122 | NonEmptyStringLiteral=122 123 | EmptyStringLiteral=123 124 | UnicodeStringLiteral=124 125 | HexString=125 126 | HexNumber=126 127 | OctalNumber=127 128 | DecimalNumber=128 129 | DecimalNumberFollowedByIdentifier=129 130 | Identifier=130 131 | WS=131 132 | COMMENT=132 133 | LINE_COMMENT=133 134 | AssemblyDialect=134 135 | AssemblyLBrace=135 136 | AssemblyFlagString=136 137 | AssemblyBlockLParen=137 138 | AssemblyBlockRParen=138 139 | AssemblyBlockComma=139 140 | AssemblyBlockWS=140 141 | AssemblyBlockCOMMENT=141 142 | AssemblyBlockLINE_COMMENT=142 143 | YulBreak=143 144 | YulCase=144 145 | YulContinue=145 146 | YulDefault=146 147 | YulFalse=147 148 | YulFor=148 149 | YulFunction=149 150 | YulIf=150 151 | YulLeave=151 152 | YulLet=152 153 | YulSwitch=153 154 | YulTrue=154 155 | YulHex=155 156 | YulEVMBuiltin=156 157 | YulLBrace=157 158 | YulRBrace=158 159 | YulLParen=159 160 | YulRParen=160 161 | YulAssign=161 162 | YulPeriod=162 163 | YulComma=163 164 | YulArrow=164 165 | YulIdentifier=165 166 | YulHexNumber=166 167 | YulDecimalNumber=167 168 | YulStringLiteral=168 169 | YulHexStringLiteral=169 170 | YulWS=170 171 | YulCOMMENT=171 172 | YulLINE_COMMENT=172 173 | PragmaToken=173 174 | PragmaSemicolon=174 175 | PragmaWS=175 176 | PragmaCOMMENT=176 177 | PragmaLINE_COMMENT=177 178 | 'abstract'=2 179 | 'address'=3 180 | 'anonymous'=4 181 | 'as'=5 182 | 'assembly'=6 183 | 'bool'=7 184 | 'bytes'=9 185 | 'calldata'=10 186 | 'catch'=11 187 | 'constant'=12 188 | 'constructor'=13 189 | 'contract'=15 190 | 'delete'=16 191 | 'do'=17 192 | 'else'=18 193 | 'emit'=19 194 | 'enum'=20 195 | 'error'=21 196 | 'event'=22 197 | 'external'=23 198 | 'fallback'=24 199 | 'from'=29 200 | 'global'=31 201 | 'immutable'=34 202 | 'import'=35 203 | 'indexed'=36 204 | 'interface'=37 205 | 'internal'=38 206 | 'is'=39 207 | 'library'=40 208 | 'mapping'=41 209 | 'memory'=42 210 | 'modifier'=43 211 | 'new'=44 212 | 'override'=46 213 | 'payable'=47 214 | 'pragma'=48 215 | 'private'=49 216 | 'public'=50 217 | 'pure'=51 218 | 'receive'=52 219 | 'return'=53 220 | 'returns'=54 221 | 'revert'=55 222 | 'storage'=57 223 | 'string'=58 224 | 'struct'=59 225 | 'try'=61 226 | 'type'=62 227 | 'unchecked'=64 228 | 'unicode'=65 229 | 'using'=67 230 | 'view'=68 231 | 'virtual'=69 232 | 'while'=70 233 | '['=73 234 | ']'=74 235 | ':'=77 236 | '?'=80 237 | '=>'=81 238 | '='=83 239 | '|='=84 240 | '^='=85 241 | '&='=86 242 | '<<='=87 243 | '>>='=88 244 | '>>>='=89 245 | '+='=90 246 | '-='=91 247 | '*='=92 248 | '/='=93 249 | '%='=94 250 | '||'=96 251 | '&&'=97 252 | '|'=98 253 | '^'=99 254 | '&'=100 255 | '<<'=101 256 | '>>'=102 257 | '>>>'=103 258 | '+'=104 259 | '-'=105 260 | '*'=106 261 | '/'=107 262 | '%'=108 263 | '**'=109 264 | '=='=110 265 | '!='=111 266 | '<'=112 267 | '>'=113 268 | '<='=114 269 | '>='=115 270 | '!'=116 271 | '~'=117 272 | '++'=118 273 | '--'=119 274 | '"'=120 275 | '\''=121 276 | '"evmasm"'=134 277 | 'case'=144 278 | 'default'=146 279 | 'leave'=151 280 | 'let'=152 281 | 'switch'=153 282 | ':='=161 283 | -------------------------------------------------------------------------------- /src/antlr4/SolidityParser.tokens: -------------------------------------------------------------------------------- 1 | ReservedKeywords=1 2 | Abstract=2 3 | Address=3 4 | Anonymous=4 5 | As=5 6 | Assembly=6 7 | Bool=7 8 | Break=8 9 | Bytes=9 10 | Calldata=10 11 | Catch=11 12 | Constant=12 13 | Constructor=13 14 | Continue=14 15 | Contract=15 16 | Delete=16 17 | Do=17 18 | Else=18 19 | Emit=19 20 | Enum=20 21 | Error=21 22 | Event=22 23 | External=23 24 | Fallback=24 25 | False=25 26 | Fixed=26 27 | FixedBytes=27 28 | For=28 29 | From=29 30 | Function=30 31 | Global=31 32 | Hex=32 33 | If=33 34 | Immutable=34 35 | Import=35 36 | Indexed=36 37 | Interface=37 38 | Internal=38 39 | Is=39 40 | Library=40 41 | Mapping=41 42 | Memory=42 43 | Modifier=43 44 | New=44 45 | SubDenomination=45 46 | Override=46 47 | Payable=47 48 | Pragma=48 49 | Private=49 50 | Public=50 51 | Pure=51 52 | Receive=52 53 | Return=53 54 | Returns=54 55 | Revert=55 56 | SignedIntegerType=56 57 | Storage=57 58 | String=58 59 | Struct=59 60 | True=60 61 | Try=61 62 | Type=62 63 | Ufixed=63 64 | Unchecked=64 65 | Unicode=65 66 | UnsignedIntegerType=66 67 | Using=67 68 | View=68 69 | Virtual=69 70 | While=70 71 | LParen=71 72 | RParen=72 73 | LBrack=73 74 | RBrack=74 75 | LBrace=75 76 | RBrace=76 77 | Colon=77 78 | Semicolon=78 79 | Period=79 80 | Conditional=80 81 | DoubleArrow=81 82 | RightArrow=82 83 | Assign=83 84 | AssignBitOr=84 85 | AssignBitXor=85 86 | AssignBitAnd=86 87 | AssignShl=87 88 | AssignSar=88 89 | AssignShr=89 90 | AssignAdd=90 91 | AssignSub=91 92 | AssignMul=92 93 | AssignDiv=93 94 | AssignMod=94 95 | Comma=95 96 | Or=96 97 | And=97 98 | BitOr=98 99 | BitXor=99 100 | BitAnd=100 101 | Shl=101 102 | Sar=102 103 | Shr=103 104 | Add=104 105 | Sub=105 106 | Mul=106 107 | Div=107 108 | Mod=108 109 | Exp=109 110 | Equal=110 111 | NotEqual=111 112 | LessThan=112 113 | GreaterThan=113 114 | LessThanOrEqual=114 115 | GreaterThanOrEqual=115 116 | Not=116 117 | BitNot=117 118 | Inc=118 119 | Dec=119 120 | DoubleQuote=120 121 | SingleQuote=121 122 | NonEmptyStringLiteral=122 123 | EmptyStringLiteral=123 124 | UnicodeStringLiteral=124 125 | HexString=125 126 | HexNumber=126 127 | OctalNumber=127 128 | DecimalNumber=128 129 | DecimalNumberFollowedByIdentifier=129 130 | Identifier=130 131 | WS=131 132 | COMMENT=132 133 | LINE_COMMENT=133 134 | AssemblyDialect=134 135 | AssemblyLBrace=135 136 | AssemblyFlagString=136 137 | AssemblyBlockLParen=137 138 | AssemblyBlockRParen=138 139 | AssemblyBlockComma=139 140 | AssemblyBlockWS=140 141 | AssemblyBlockCOMMENT=141 142 | AssemblyBlockLINE_COMMENT=142 143 | YulBreak=143 144 | YulCase=144 145 | YulContinue=145 146 | YulDefault=146 147 | YulFalse=147 148 | YulFor=148 149 | YulFunction=149 150 | YulIf=150 151 | YulLeave=151 152 | YulLet=152 153 | YulSwitch=153 154 | YulTrue=154 155 | YulHex=155 156 | YulEVMBuiltin=156 157 | YulLBrace=157 158 | YulRBrace=158 159 | YulLParen=159 160 | YulRParen=160 161 | YulAssign=161 162 | YulPeriod=162 163 | YulComma=163 164 | YulArrow=164 165 | YulIdentifier=165 166 | YulHexNumber=166 167 | YulDecimalNumber=167 168 | YulStringLiteral=168 169 | YulHexStringLiteral=169 170 | YulWS=170 171 | YulCOMMENT=171 172 | YulLINE_COMMENT=172 173 | PragmaToken=173 174 | PragmaSemicolon=174 175 | PragmaWS=175 176 | PragmaCOMMENT=176 177 | PragmaLINE_COMMENT=177 178 | 'abstract'=2 179 | 'address'=3 180 | 'anonymous'=4 181 | 'as'=5 182 | 'assembly'=6 183 | 'bool'=7 184 | 'bytes'=9 185 | 'calldata'=10 186 | 'catch'=11 187 | 'constant'=12 188 | 'constructor'=13 189 | 'contract'=15 190 | 'delete'=16 191 | 'do'=17 192 | 'else'=18 193 | 'emit'=19 194 | 'enum'=20 195 | 'error'=21 196 | 'event'=22 197 | 'external'=23 198 | 'fallback'=24 199 | 'from'=29 200 | 'global'=31 201 | 'immutable'=34 202 | 'import'=35 203 | 'indexed'=36 204 | 'interface'=37 205 | 'internal'=38 206 | 'is'=39 207 | 'library'=40 208 | 'mapping'=41 209 | 'memory'=42 210 | 'modifier'=43 211 | 'new'=44 212 | 'override'=46 213 | 'payable'=47 214 | 'pragma'=48 215 | 'private'=49 216 | 'public'=50 217 | 'pure'=51 218 | 'receive'=52 219 | 'return'=53 220 | 'returns'=54 221 | 'revert'=55 222 | 'storage'=57 223 | 'string'=58 224 | 'struct'=59 225 | 'try'=61 226 | 'type'=62 227 | 'unchecked'=64 228 | 'unicode'=65 229 | 'using'=67 230 | 'view'=68 231 | 'virtual'=69 232 | 'while'=70 233 | '['=73 234 | ']'=74 235 | ':'=77 236 | '?'=80 237 | '=>'=81 238 | '='=83 239 | '|='=84 240 | '^='=85 241 | '&='=86 242 | '<<='=87 243 | '>>='=88 244 | '>>>='=89 245 | '+='=90 246 | '-='=91 247 | '*='=92 248 | '/='=93 249 | '%='=94 250 | '||'=96 251 | '&&'=97 252 | '|'=98 253 | '^'=99 254 | '&'=100 255 | '<<'=101 256 | '>>'=102 257 | '>>>'=103 258 | '+'=104 259 | '-'=105 260 | '*'=106 261 | '/'=107 262 | '%'=108 263 | '**'=109 264 | '=='=110 265 | '!='=111 266 | '<'=112 267 | '>'=113 268 | '<='=114 269 | '>='=115 270 | '!'=116 271 | '~'=117 272 | '++'=118 273 | '--'=119 274 | '"'=120 275 | '\''=121 276 | '"evmasm"'=134 277 | 'case'=144 278 | 'default'=146 279 | 'leave'=151 280 | 'let'=152 281 | 'switch'=153 282 | ':='=161 283 | -------------------------------------------------------------------------------- /src/antlr4/index.ts: -------------------------------------------------------------------------------- 1 | export * from 'antlr4ng'; 2 | export * from './SolidityLexer'; 3 | export * from './SolidityParser'; 4 | export * from './SolidityParserListener'; 5 | export * from './SolidityParserVisitor'; 6 | -------------------------------------------------------------------------------- /src/ast/base.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-use-before-define */ 2 | import { SyntaxNode, SyntaxNodeType } from './index'; 3 | import { ParseTree, ParserRuleContext, SolidityParserVisitor } from '../antlr4'; 4 | 5 | export class Position { 6 | static create(line: number, column: number): Position { 7 | return new Position(line, column); 8 | } 9 | constructor( 10 | // 1-based 11 | public line: number, 12 | // 0-based 13 | public column: number, 14 | ) {} 15 | } 16 | 17 | export class Location { 18 | static create(start: Position, end: Position, source?: `${number}:${number}`): Location { 19 | return new Location(start, end, source); 20 | } 21 | constructor( 22 | public start: Position, 23 | public end: Position, 24 | public source?: `${number}:${number}`, // `{start}:{length}` 25 | ) {} 26 | } 27 | 28 | export type UnionSyntaxNode> = InstanceType; 29 | export type UnionSyntaxNodeType> = keyof T; 30 | export type LookUp = U extends infer P 31 | ? P extends { type: any } 32 | ? T extends P['type'] 33 | ? P 34 | : never 35 | : never 36 | : never; 37 | 38 | export const formatString = (str: string) => { 39 | return str.substring(1, str.length - 1); 40 | }; 41 | 42 | export const isSyntaxNode = (node: any): node is T => { 43 | return ( 44 | (node instanceof BaseNode || node instanceof BaseNodeString || node instanceof BaseNodeUnion) && 45 | !!node.type 46 | ); 47 | }; 48 | 49 | export const isSyntaxNodeList = (node: any): node is T => { 50 | return node instanceof BaseNodeList || (Array.isArray(node) && node.every(isSyntaxNode)); 51 | }; 52 | 53 | export const keysInNode = (node: T): string[] => { 54 | const forbiddenKeys = ['context', 'serialize', 'print']; 55 | const keys: string[] = []; 56 | 57 | for (const key in node) { 58 | if (Object.prototype.hasOwnProperty.call(node, key) && !forbiddenKeys.includes(key)) { 59 | keys.push(key); 60 | } 61 | } 62 | return keys; 63 | }; 64 | 65 | export abstract class BaseNode { 66 | type: SyntaxNodeType; 67 | range: [number, number]; 68 | location: Location; 69 | 70 | constructor(ctx: ParserRuleContext, _visitor: SolidityParserVisitor) { 71 | const start = ctx.start?.start ?? 0; 72 | const end = ctx.stop?.stop ?? start; 73 | this.range = [start, end]; 74 | 75 | const startPosition = Position.create(ctx.start?.line ?? 1, ctx.start?.column ?? 0); 76 | const endPosition = Position.create( 77 | ctx.stop?.line ?? startPosition.line, 78 | ctx.stop?.column ?? startPosition.column, 79 | ); 80 | this.location = Location.create(startPosition, endPosition, `${start}:${end - start}`); 81 | // this.context = ctx; 82 | } 83 | 84 | /** @ignore */ 85 | // context: ParserRuleContext; 86 | 87 | /** @ignore */ 88 | // print?: SyntaxPrint; 89 | } 90 | 91 | export abstract class BaseNodeList extends Array { 92 | constructor( 93 | ctxList: (ParseTree | any)[], 94 | visitor: SolidityParserVisitor, 95 | formatter: (item: ParseTree) => T = (ctx) => ctx.accept(visitor!), 96 | ) { 97 | super(...ctxList.map(formatter)); 98 | } 99 | } 100 | 101 | export abstract class BaseNodeString extends BaseNode { 102 | name: string | any; 103 | constructor(ctx: ParserRuleContext, visitor: SolidityParserVisitor) { 104 | super(ctx, visitor); 105 | this.name = ctx.getText(); 106 | } 107 | } 108 | 109 | export abstract class BaseNodeUnion< 110 | T extends BaseNode | BaseNodeList = BaseNode, 111 | > extends BaseNode { 112 | constructor( 113 | _ctx: ParserRuleContext, 114 | list: (ParseTree | null)[] | null, 115 | visitor: SolidityParserVisitor, 116 | ) { 117 | super(_ctx, visitor); 118 | const target = (list ?? []).find(Boolean); 119 | if (target) { 120 | // @ts-expect-error 121 | return target.accept(visitor) as T; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/ast/declaration/contract-body-element.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeUnion } from '../base'; 2 | import { ContractBodyElementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { 4 | ConstructorDefinition, 5 | FallbackFunctionDefinition, 6 | FunctionDefinition, 7 | ReceiveFunctionDefinition, 8 | } from './function-definition'; 9 | import { ModifierDefinition } from './modifier-definition'; 10 | import { StructDefinition } from './struct-definition'; 11 | import { EnumDefinition } from './enum-definition'; 12 | import { UserDefinedValueTypeDefinition } from './user-defined-value-type-definition'; 13 | import { StateVariableDeclaration } from './variable-declaration'; 14 | import { EventDefinition } from './event-definition'; 15 | import { ErrorDefinition } from './error-definition'; 16 | import { UsingDirective } from '../meta'; 17 | 18 | export type ContractBodyElementNode = 19 | | ConstructorDefinition 20 | | FunctionDefinition 21 | | ModifierDefinition 22 | | FallbackFunctionDefinition 23 | | ReceiveFunctionDefinition 24 | | StructDefinition 25 | | EnumDefinition 26 | | UserDefinedValueTypeDefinition 27 | | StateVariableDeclaration 28 | | EventDefinition 29 | | ErrorDefinition 30 | | UsingDirective; 31 | 32 | export class ContractBodyElement extends BaseNodeUnion< 33 | | ConstructorDefinition 34 | | FunctionDefinition 35 | | ModifierDefinition 36 | | FallbackFunctionDefinition 37 | | ReceiveFunctionDefinition 38 | | StructDefinition 39 | | EnumDefinition 40 | | UserDefinedValueTypeDefinition 41 | | StateVariableDeclaration 42 | | EventDefinition 43 | | ErrorDefinition 44 | | UsingDirective 45 | > { 46 | // type = 'ContractBodyElement' as const; 47 | constructor(ctx: ContractBodyElementContext, visitor: SolidityParserVisitor) { 48 | super( 49 | ctx, 50 | [ 51 | ctx.constructorDefinition(), 52 | ctx.functionDefinition(), 53 | ctx.modifierDefinition(), 54 | ctx.fallbackFunctionDefinition(), 55 | ctx.receiveFunctionDefinition(), 56 | ctx.structDefinition(), 57 | ctx.enumDefinition(), 58 | ctx.userDefinedValueTypeDefinition(), 59 | ctx.stateVariableDeclaration(), 60 | ctx.stateVariableDeclaration(), 61 | ctx.eventDefinition(), 62 | ctx.errorDefinition(), 63 | ctx.usingDirective(), 64 | ], 65 | visitor, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ast/declaration/contract-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { 3 | ContractDefinitionContext, 4 | InterfaceDefinitionContext, 5 | LibraryDefinitionContext, 6 | SolidityParserVisitor, 7 | } from '../../antlr4'; 8 | import { EnumDefinition } from './enum-definition'; 9 | import { ErrorDefinition } from './error-definition'; 10 | import { EventDefinition } from './event-definition'; 11 | import { FunctionDefinition } from './function-definition'; 12 | import { ModifierDefinition } from './modifier-definition'; 13 | import { StructDefinition } from './struct-definition'; 14 | import { UserDefinedValueTypeDefinition } from './user-defined-value-type-definition'; 15 | import { UsingDirective, InheritanceSpecifier } from '../meta'; 16 | import { VariableDeclaration } from './variable-declaration'; 17 | import { Identifier } from '../expression'; 18 | 19 | type ContractKind = 'contract' | 'interface' | 'library'; 20 | 21 | type ContractDefinitionNodes = 22 | | EnumDefinition 23 | | ErrorDefinition 24 | | EventDefinition 25 | | FunctionDefinition 26 | | ModifierDefinition 27 | | StructDefinition 28 | | UserDefinedValueTypeDefinition 29 | | UsingDirective 30 | | VariableDeclaration; 31 | 32 | export class ContractDefinition extends BaseNode { 33 | type = 'ContractDefinition' as const; 34 | /** 35 | * The contract name 36 | */ 37 | name: Identifier; 38 | /** 39 | * Type of contract declaration, e.g. `contract`, `library` or `interface`. 40 | */ 41 | contractKind: ContractKind = 'contract'; 42 | /** 43 | * Is `true` if contract is declared as an abstract 44 | * (using `abstract` keyword since Solidity 0.6). 45 | * 46 | * Is `false` otherwise. 47 | */ 48 | abstract: boolean = false; 49 | /** 50 | * Base contracts 51 | */ 52 | baseContracts: InheritanceSpecifier[] = []; 53 | nodes: ContractDefinitionNodes[] = []; 54 | constructor( 55 | ctx: ContractDefinitionContext | InterfaceDefinitionContext | LibraryDefinitionContext, 56 | visitor: SolidityParserVisitor, 57 | ) { 58 | super(ctx, visitor); 59 | this.name = visitor.visit(ctx.identifier()) as Identifier; 60 | this.nodes = ctx.contractBodyElement().map((element) => element.accept(visitor)); 61 | 62 | if (ctx instanceof InterfaceDefinitionContext) { 63 | this.contractKind = 'interface'; 64 | this.baseContracts = ctx.inheritanceSpecifierList()?.accept(visitor) ?? []; 65 | } else if (ctx instanceof LibraryDefinitionContext) { 66 | this.contractKind = 'library'; 67 | } else { 68 | this.contractKind = 'contract'; 69 | this.abstract = !!ctx.Abstract(); 70 | this.baseContracts = ctx.inheritanceSpecifierList()?.accept(visitor) ?? []; 71 | } 72 | } 73 | } 74 | 75 | export { ContractDefinition as InterfaceDefinition, ContractDefinition as LibraryDefinition }; 76 | -------------------------------------------------------------------------------- /src/ast/declaration/enum-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { EnumDefinitionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | 5 | export class EnumDefinition extends BaseNode { 6 | type = 'EnumDefinition' as const; 7 | name: Identifier; 8 | members: Identifier[]; 9 | constructor(ctx: EnumDefinitionContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | const [name, ...members] = ctx.identifier(); 12 | this.name = name.accept(visitor); 13 | this.members = members.map((m) => m.accept(visitor)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/declaration/error-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ErrorDefinitionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { ErrorParameter } from './variable-declaration'; 4 | import { Identifier } from '../expression'; 5 | 6 | export class ErrorDefinition extends BaseNode { 7 | type = 'ErrorDefinition' as const; 8 | name: Identifier; 9 | parameters: ErrorParameter[]; 10 | constructor(ctx: ErrorDefinitionContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.name = ctx.identifier().accept(visitor); 13 | this.parameters = ctx.errorParameter().map((param) => param.accept(visitor)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/declaration/event-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { EventDefinitionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { EventParameter } from './variable-declaration'; 4 | import { Identifier } from '../expression'; 5 | 6 | export class EventDefinition extends BaseNode { 7 | type = 'EventDefinition' as const; 8 | name: Identifier; 9 | anonymous: boolean; 10 | parameters: EventParameter[] = []; 11 | constructor(ctx: EventDefinitionContext, visitor: SolidityParserVisitor) { 12 | super(ctx, visitor); 13 | this.name = ctx.identifier().accept(visitor); 14 | this.anonymous = !!ctx.Anonymous(); 15 | this.parameters = ctx.eventParameter().map((param) => param.accept(visitor)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ast/declaration/function-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { 3 | ConstructorDefinitionContext, 4 | FallbackFunctionDefinitionContext, 5 | FunctionDefinitionContext, 6 | FunctionTypeNameContext, 7 | OverrideSpecifierContext, 8 | ReceiveFunctionDefinitionContext, 9 | SolidityParserVisitor, 10 | } from '../../antlr4'; 11 | import { Identifier } from '../expression'; 12 | import { StateMutabilityKind } from './state-mutability'; 13 | import { VisibilityKind } from './visibility'; 14 | import { ModifierInvocation, OverrideSpecifier, ParameterList } from '../meta'; 15 | import { Block } from '../statement'; 16 | 17 | type FunctionKind = 'function' | 'constructor' | 'receive' | 'fallback'; 18 | 19 | export class BaseFunctionDefinition extends BaseNode { 20 | type = 'FunctionDefinition' as any; 21 | name: Identifier | null = null; 22 | functionKind: FunctionKind = 'function'; 23 | override: OverrideSpecifier | null = null; 24 | virtual: boolean = false; 25 | visibility: VisibilityKind | null = null; 26 | stateMutability: StateMutabilityKind | null = null; 27 | modifiers: ModifierInvocation[] | null = null; 28 | parameters: ParameterList | null = null; 29 | returnParameters: ParameterList | null = null; 30 | body: Block | null = null; 31 | 32 | constructor( 33 | ctx: 34 | | FunctionDefinitionContext 35 | | FunctionTypeNameContext 36 | | ConstructorDefinitionContext 37 | | FallbackFunctionDefinitionContext 38 | | ReceiveFunctionDefinitionContext, 39 | visitor: SolidityParserVisitor, 40 | ) { 41 | super(ctx, visitor); 42 | if (ctx instanceof FunctionTypeNameContext) { 43 | this.functionKind = 'function'; 44 | this.visibility = (ctx.visibility(0)?.getText() as VisibilityKind) ?? null; // ctx.visibility(0)?.accept(visitor) ?? null; 45 | this.stateMutability = (ctx.stateMutability(0)?.getText() as StateMutabilityKind) ?? null; // ctx.stateMutability(0)?.accept(visitor) ?? null; 46 | this.parameters = ctx._arguments?.accept(visitor) ?? null; 47 | this.returnParameters = ctx._returnParameters?.accept(visitor) ?? null; 48 | } else { 49 | this.modifiers = ctx.modifierInvocation().map((modifier) => modifier.accept(visitor)); 50 | this.body = ctx.block()?.accept(visitor) ?? null; 51 | 52 | if (ctx instanceof ConstructorDefinitionContext) { 53 | this.functionKind = 'constructor'; 54 | this.visibility = ctx.Internal(0) ? 'internal' : ctx.Public(0) ? 'public' : null; // new Visibility(ctx as any, visitor); 55 | this.stateMutability = ctx.Payable(0) ? 'payable' : null; // new StateMutability(ctx as any, visitor); 56 | this.parameters = ctx._arguments?.accept(visitor) ?? null; 57 | this.returnParameters = null; 58 | } else { 59 | this.virtual = !!ctx.Virtual().length; 60 | const overrideSpecifier = ctx.overrideSpecifier(0) as OverrideSpecifierContext | null; 61 | this.override = overrideSpecifier ? overrideSpecifier.accept(visitor) : null; 62 | 63 | if (ctx instanceof FallbackFunctionDefinitionContext) { 64 | this.functionKind = 'fallback'; 65 | this.visibility = ctx.External(0) ? 'external' : null; // new Visibility(ctx as any, visitor); 66 | this.stateMutability = (ctx.stateMutability(0)?.getText() as StateMutabilityKind) ?? null; 67 | this.parameters = ctx 68 | .parameterList() 69 | .map((parameterList) => parameterList.accept(visitor)); 70 | this.returnParameters = ctx._returnParameters?.accept(visitor) ?? null; 71 | } else if (ctx instanceof ReceiveFunctionDefinitionContext) { 72 | this.functionKind = 'receive'; 73 | this.visibility = ctx.External() ? 'external' : null; // new Visibility(ctx as any, visitor); 74 | this.stateMutability = ctx.Payable() ? 'payable' : null; // new StateMutability(ctx as any, visitor); 75 | } else { 76 | this.functionKind = 'function'; 77 | this.name = ctx.identifier()?.accept(visitor) ?? null; 78 | this.visibility = (ctx.visibility(0)?.getText() as VisibilityKind) ?? null; 79 | this.stateMutability = (ctx.stateMutability(0)?.getText() as StateMutabilityKind) ?? null; 80 | this.parameters = ctx._arguments?.accept(visitor) ?? null; 81 | this.returnParameters = ctx._returnParameters?.accept(visitor) ?? null; 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | export class FunctionDefinition extends BaseFunctionDefinition { 89 | type = 'FunctionDefinition' as const; 90 | } 91 | 92 | export { 93 | FunctionDefinition as ConstructorDefinition, 94 | FunctionDefinition as FallbackFunctionDefinition, 95 | FunctionDefinition as ReceiveFunctionDefinition, 96 | }; 97 | -------------------------------------------------------------------------------- /src/ast/declaration/index.node.ts: -------------------------------------------------------------------------------- 1 | import { ContractDefinition } from './contract-definition'; 2 | import { EnumDefinition } from './enum-definition'; 3 | import { ErrorDefinition } from './error-definition'; 4 | import { EventDefinition } from './event-definition'; 5 | import { FunctionDefinition } from './function-definition'; 6 | import { ModifierDefinition } from './modifier-definition'; 7 | // import { StateMutability } from './state-mutability'; 8 | import { StructDefinition } from './struct-definition'; 9 | import { StructMember } from './struct-member'; 10 | import { UserDefinedValueTypeDefinition } from './user-defined-value-type-definition'; 11 | import { VariableDeclaration } from './variable-declaration'; 12 | // import { Visibility } from './visibility'; 13 | 14 | export { 15 | ContractDefinition, 16 | EnumDefinition, 17 | ErrorDefinition, 18 | EventDefinition, 19 | FunctionDefinition, 20 | ModifierDefinition, 21 | // StateMutability, 22 | StructDefinition, 23 | StructMember, 24 | UserDefinedValueTypeDefinition, 25 | VariableDeclaration, 26 | // Visibility, 27 | }; 28 | -------------------------------------------------------------------------------- /src/ast/declaration/index.ts: -------------------------------------------------------------------------------- 1 | import * as nodeMap from './index.node'; 2 | import { UnionSyntaxNode, UnionSyntaxNodeType } from '../base'; 3 | 4 | export type DeclarationNode = UnionSyntaxNode; 5 | export type DeclarationNodeType = UnionSyntaxNodeType; 6 | 7 | export const declarationNodeTypes = Object.keys(nodeMap) as DeclarationNodeType[]; 8 | export * from './contract-body-element'; 9 | export * from './contract-definition'; 10 | export * from './enum-definition'; 11 | export * from './error-definition'; 12 | export * from './event-definition'; 13 | export * from './function-definition'; 14 | export * from './modifier-definition'; 15 | export * from './state-mutability'; 16 | export * from './struct-definition'; 17 | export * from './struct-member'; 18 | export * from './user-defined-value-type-definition'; 19 | export * from './variable-declaration-list'; 20 | export * from './variable-declaration-tuple'; 21 | export * from './variable-declaration'; 22 | export * from './visibility'; 23 | -------------------------------------------------------------------------------- /src/ast/declaration/modifier-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ModifierDefinitionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | import { OverrideSpecifier, ParameterList } from '../meta'; 5 | import { Block } from '../statement'; 6 | 7 | export class ModifierDefinition extends BaseNode { 8 | type = 'ModifierDefinition' as const; 9 | name: Identifier; 10 | parameters: ParameterList | null = null; 11 | virtual: boolean = false; 12 | override: OverrideSpecifier | null = null; 13 | body: Block | null = null; 14 | constructor(ctx: ModifierDefinitionContext, visitor: SolidityParserVisitor) { 15 | super(ctx, visitor); 16 | this.name = ctx.identifier().accept(visitor); 17 | this.parameters = ctx.parameterList()?.accept(visitor) ?? null; 18 | this.virtual = !!ctx.Virtual().length; 19 | this.override = ctx.overrideSpecifier(0)?.accept(visitor) ?? null; 20 | this.body = ctx.block()?.accept(visitor) ?? null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ast/declaration/state-mutability.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { StateMutabilityContext, SolidityParserVisitor, TerminalNode } from '../../antlr4'; 3 | 4 | export type StateMutabilityKind = 'pure' | 'view' | 'payable'; 5 | 6 | export class StateMutability extends BaseNodeString { 7 | // type = 'StateMutability' as const; 8 | name: StateMutabilityKind | null = null; 9 | 10 | constructor(ctx: StateMutabilityContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | const format = (n: TerminalNode | TerminalNode[] | null) => { 13 | if (Array.isArray(n) && !!n.length) { 14 | return true; 15 | } else if (!!n) { 16 | return true; 17 | } 18 | return false; 19 | }; 20 | if (format(ctx.Payable?.())) { 21 | this.name = 'payable'; 22 | } else if (format(ctx.Pure?.())) { 23 | this.name = 'pure'; 24 | } else if (format(ctx.View?.())) { 25 | this.name = 'view'; 26 | } else { 27 | this.name = null; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ast/declaration/struct-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { StructDefinitionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | import { StructMember } from './struct-member'; 5 | 6 | export class StructDefinition extends BaseNode { 7 | type = 'StructDefinition' as const; 8 | name: Identifier; 9 | members: StructMember[]; 10 | constructor(ctx: StructDefinitionContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.name = ctx.identifier().accept(visitor); 13 | this.members = ctx.structMember().map((member) => member.accept(visitor)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/declaration/struct-member.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { StructMemberContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | import { TypeName } from '../type'; 5 | 6 | export class StructMember extends BaseNode { 7 | type = 'StructMember' as const; 8 | name: Identifier; 9 | typeName: TypeName; 10 | constructor(ctx: StructMemberContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.name = ctx.identifier().accept(visitor); 13 | this.typeName = ctx.typeName().accept(visitor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/declaration/user-defined-value-type-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { UserDefinedValueTypeDefinitionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { ElementaryTypeName } from '../type'; 4 | import { Identifier } from '../expression'; 5 | 6 | export class UserDefinedValueTypeDefinition extends BaseNode { 7 | type = 'UserDefinedValueTypeDefinition' as const; 8 | name: Identifier; 9 | typeName: ElementaryTypeName; 10 | constructor(ctx: UserDefinedValueTypeDefinitionContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.name = ctx.identifier().accept(visitor); 13 | this.typeName = ctx.elementaryTypeName().accept(visitor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/declaration/variable-declaration-list.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList } from '../base'; 2 | import { VariableDeclarationListContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { VariableDeclaration } from './variable-declaration'; 4 | 5 | export type VariableDeclarationList = VariableDeclaration[]; 6 | 7 | export const VariableDeclarationList = class extends BaseNodeList { 8 | type = 'VariableDeclarationList' as const; 9 | constructor(ctx: VariableDeclarationListContext, visitor: SolidityParserVisitor) { 10 | super(ctx.variableDeclaration(), visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/declaration/variable-declaration-tuple.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList } from '../base'; 2 | import { 3 | VariableDeclarationTupleContext, 4 | SolidityParserVisitor, 5 | VariableDeclarationContext, 6 | } from '../../antlr4'; 7 | import { VariableDeclaration } from './variable-declaration'; 8 | 9 | export type VariableDeclarationTuple = (VariableDeclaration | null)[]; 10 | 11 | const isVarCtx = (ctx: any): ctx is VariableDeclarationContext => 12 | ctx instanceof VariableDeclarationContext; 13 | 14 | export const VariableDeclarationTuple = class extends BaseNodeList { 15 | type = 'VariableDeclarationTuple' as const; 16 | constructor(ctx: VariableDeclarationTupleContext, visitor: SolidityParserVisitor) { 17 | const list: (VariableDeclarationContext | null)[] = []; 18 | const children = ctx.children ?? []; 19 | for (let index = 0; index < children.length; index += 1) { 20 | const current = children[index]; 21 | const next = children[index + 1]; 22 | if (!next) continue; 23 | if (!isVarCtx(current) && isVarCtx(next)) { 24 | list.push(next); 25 | } else if (!isVarCtx(current) && !isVarCtx(next)) { 26 | list.push(null); 27 | } 28 | } 29 | super(list, visitor, (current) => (current === null ? null : current.accept(visitor!))); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/ast/declaration/variable-declaration.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { 3 | VariableDeclarationContext, 4 | SolidityParserVisitor, 5 | ParameterDeclarationContext, 6 | StateVariableDeclarationContext, 7 | ConstantVariableDeclarationContext, 8 | EventParameterContext, 9 | ErrorParameterContext, 10 | } from '../../antlr4'; 11 | import { Identifier } from '../expression'; 12 | import { DataLocation } from '../meta'; 13 | import { TypeName } from '../type'; 14 | import { Expression } from '../expression'; 15 | import { OverrideSpecifier } from '../meta'; 16 | 17 | export class VariableDeclaration extends BaseNode { 18 | type = 'VariableDeclaration' as const; 19 | name: Identifier | null = null; 20 | typeName: TypeName; 21 | dataLocation: DataLocation | null = null; 22 | 23 | stateVariable: boolean = false; 24 | parameter: boolean = false; 25 | 26 | public: boolean = false; 27 | private: boolean = false; 28 | internal: boolean = false; 29 | constant: boolean = false; 30 | immutable: boolean = false; 31 | indexed: boolean = false; 32 | 33 | override: OverrideSpecifier | null = null; 34 | expression: Expression | null = null; 35 | 36 | constructor( 37 | ctx: 38 | | VariableDeclarationContext 39 | | ParameterDeclarationContext 40 | | StateVariableDeclarationContext 41 | | ConstantVariableDeclarationContext 42 | | ErrorParameterContext 43 | | EventParameterContext, 44 | visitor: SolidityParserVisitor, 45 | ) { 46 | super(ctx, visitor); 47 | 48 | if (ctx instanceof VariableDeclarationContext) { 49 | this.parameter = true; // from the grammar 50 | this.name = ctx.identifier()?.accept(visitor) ?? null; 51 | this.typeName = ctx.typeName().accept(visitor); 52 | this.dataLocation = ctx.dataLocation()?.accept(visitor) ?? null; 53 | this.stateVariable = false; 54 | this.constant = false; 55 | this.indexed = false; 56 | } else if (ctx instanceof ParameterDeclarationContext) { 57 | this.parameter = true; 58 | this.name = ctx.identifier()?.accept(visitor) ?? null; 59 | this.typeName = ctx.typeName().accept(visitor); 60 | this.dataLocation = ctx.dataLocation()?.accept(visitor) ?? null; 61 | } else if (ctx instanceof StateVariableDeclarationContext) { 62 | this.stateVariable = true; 63 | this.name = ctx.identifier()?.accept(visitor) ?? null; 64 | this.typeName = ctx.typeName().accept(visitor); 65 | this.public = !!ctx.Public().length; 66 | this.private = !!ctx.Private().length; 67 | this.internal = !!ctx.Internal().length; 68 | this.constant = !!ctx.Constant().length; 69 | this.immutable = !!ctx.Immutable().length; 70 | this.override = ctx.overrideSpecifier(0)?.accept(visitor) ?? null; 71 | this.expression = ctx.expression()?.accept(visitor) ?? null; 72 | } else if (ctx instanceof ConstantVariableDeclarationContext) { 73 | this.name = ctx.identifier()?.accept(visitor) ?? null; 74 | this.typeName = ctx.typeName().accept(visitor); 75 | this.constant = true; 76 | this.expression = ctx.expression().accept(visitor); 77 | } else if (ctx instanceof ErrorParameterContext) { 78 | this.parameter = true; 79 | this.name = ctx.identifier()?.accept(visitor) ?? null; 80 | this.typeName = ctx.typeName().accept(visitor); 81 | } else if (ctx instanceof EventParameterContext) { 82 | this.parameter = true; 83 | this.name = ctx.identifier()?.accept(visitor) ?? null; 84 | this.typeName = ctx.typeName().accept(visitor); 85 | this.indexed = !!ctx.Indexed(); 86 | } 87 | } 88 | } 89 | 90 | export { 91 | VariableDeclaration as ParameterDeclaration, 92 | VariableDeclaration as StateVariableDeclaration, 93 | VariableDeclaration as ConstantVariableDeclaration, 94 | VariableDeclaration as ErrorParameter, 95 | VariableDeclaration as EventParameter, 96 | }; 97 | -------------------------------------------------------------------------------- /src/ast/declaration/visibility.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { VisibilityContext, SolidityParserVisitor, TerminalNode } from '../../antlr4'; 3 | 4 | export type VisibilityKind = 'external' | 'internal' | 'public' | 'private'; 5 | 6 | export class Visibility extends BaseNodeString { 7 | // type = 'Visibility' as const; 8 | name: VisibilityKind | null = null; 9 | constructor(ctx: VisibilityContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | const format = (n: TerminalNode | TerminalNode[] | null | undefined) => { 12 | if (Array.isArray(n)) { 13 | return !!n.length; 14 | } else if (!!n?.symbol) { 15 | return true; 16 | } 17 | return false; 18 | }; 19 | 20 | if (format(ctx.External?.())) { 21 | this.name = 'external'; 22 | } else if (format(ctx.Internal?.())) { 23 | this.name = 'internal'; 24 | } else if (format(ctx.Private?.())) { 25 | this.name = 'private'; 26 | } else if (format(ctx.Public?.())) { 27 | this.name = 'public'; 28 | } else { 29 | this.name = null; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ast/expression/assign-op.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | 3 | export class AssignOp extends BaseNodeString { 4 | type = 'AssignOp' as const; 5 | } 6 | -------------------------------------------------------------------------------- /src/ast/expression/assignment.ts: -------------------------------------------------------------------------------- 1 | import { BinaryOperation } from './binary-operation'; 2 | 3 | export class Assignment extends BinaryOperation { 4 | // @ts-expect-error 5 | type = 'Assignment' as const; 6 | } 7 | -------------------------------------------------------------------------------- /src/ast/expression/binary-operation.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ExpressionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from './expression'; 4 | 5 | export class BinaryOperation extends BaseNode { 6 | type = 'BinaryOperation' as const; 7 | operator: string | null = null; 8 | left: Expression | null = null; 9 | right: Expression | null = null; 10 | constructor(ctx: T, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.left = ctx.getChild(0)?.accept(visitor) ?? null; 13 | this.operator = ctx.getChild(1)?.getText() ?? null; 14 | this.right = ctx.getChild(2)?.accept(visitor) ?? null; 15 | } 16 | } 17 | 18 | export { 19 | BinaryOperation as AndOperation, 20 | BinaryOperation as AddSubOperation, 21 | BinaryOperation as BitAndOperation, 22 | BinaryOperation as BitOrOperation, 23 | BinaryOperation as BitXorOperation, 24 | BinaryOperation as EqualityComparison, 25 | BinaryOperation as ExpOperation, 26 | BinaryOperation as MulDivModOperation, 27 | BinaryOperation as ShiftOperation, 28 | BinaryOperation as OrderComparison, 29 | BinaryOperation as OrOperation, 30 | }; 31 | -------------------------------------------------------------------------------- /src/ast/expression/boolean-literal.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { BooleanLiteralContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class BooleanLiteral extends BaseNode { 5 | type = 'BooleanLiteral' as const; 6 | value: boolean | null = null; 7 | constructor(ctx: BooleanLiteralContext, visitor: SolidityParserVisitor) { 8 | super(ctx, visitor); 9 | this.value = ctx.True() ? true : ctx.False() ? false : null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ast/expression/call-argument-list.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { CallArgumentListContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from './expression'; 4 | import { NamedArgument } from './named-argument'; 5 | 6 | export class CallArgumentList extends BaseNode { 7 | type = 'CallArgumentList' as const; 8 | namedArguments: NamedArgument[] = []; 9 | expressions: Expression[] = []; 10 | constructor(ctx: CallArgumentListContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.namedArguments = ctx.namedArgument().map((namedArgument) => namedArgument.accept(visitor)); 13 | this.expressions = ctx.expression().map((expression) => expression.accept(visitor)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/expression/conditional.ts: -------------------------------------------------------------------------------- 1 | import { ConditionalContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { BaseNode } from '../base'; 3 | import { Expression } from './expression'; 4 | 5 | export class Conditional extends BaseNode { 6 | type = 'Conditional' as const; 7 | condition: Expression; 8 | trueExpression: Expression; 9 | falseExpression: Expression; 10 | constructor(ctx: ConditionalContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.condition = ctx.expression(0)!.accept(visitor); 13 | this.trueExpression = ctx.expression(1)!.accept(visitor); 14 | this.falseExpression = ctx.expression(2)!.accept(visitor); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ast/expression/expression.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-redeclare */ 2 | import { BaseNodeUnion } from '../base'; 3 | import { ExpressionContext, SolidityParserVisitor } from '../../antlr4'; 4 | import { ExpressionNode } from './index'; 5 | 6 | export type Expression = ExpressionNode; 7 | 8 | export const Expression = class extends BaseNodeUnion { 9 | // type = 'Expression' as any; 10 | constructor(ctx: ExpressionContext, visitor: SolidityParserVisitor) { 11 | super(ctx, ctx.children, visitor); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/ast/expression/function-call-options.ts: -------------------------------------------------------------------------------- 1 | import { FunctionCallOptionsContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { BaseNode } from '../base'; 3 | import { Expression } from './expression'; 4 | import { NamedArgument } from './named-argument'; 5 | 6 | export class FunctionCallOptions extends BaseNode { 7 | type = 'FunctionCallOptions' as const; 8 | expression: Expression; 9 | namedArguments: NamedArgument[] = []; 10 | constructor(ctx: FunctionCallOptionsContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.expression = ctx.expression().accept(visitor); 13 | this.namedArguments = ctx.namedArgument().map((arg) => arg.accept(visitor)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/expression/function-call.ts: -------------------------------------------------------------------------------- 1 | import { FunctionCallContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { Expression } from './expression'; 3 | import { CallArgumentList } from './call-argument-list'; 4 | import { BaseNode } from '../base'; 5 | 6 | export abstract class AbstractFunctionCall extends BaseNode { 7 | expression: Expression; 8 | arguments: CallArgumentList | null = null; 9 | constructor(ctx: FunctionCallContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | this.expression = ctx.expression().accept(visitor); 12 | this.arguments = ctx.callArgumentList().accept(visitor); 13 | } 14 | } 15 | 16 | export class FunctionCall extends AbstractFunctionCall { 17 | type = 'FunctionCall' as const; 18 | } 19 | -------------------------------------------------------------------------------- /src/ast/expression/hex-string-literal.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { HexStringLiteralContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class HexStringLiteral extends BaseNode { 5 | type = 'HexStringLiteral' as const; 6 | value: string; 7 | constructor(ctx: HexStringLiteralContext, visitor: SolidityParserVisitor) { 8 | super(ctx, visitor); 9 | const hexString = ctx.HexString(0)?.getText(); 10 | this.value = hexString ? hexString.substring(4, hexString.length - 1) : ''; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/expression/identifier.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { IdentifierContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class Identifier extends BaseNodeString { 5 | type = 'Identifier' as const; 6 | constructor(ctx: IdentifierContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ast/expression/index-access.ts: -------------------------------------------------------------------------------- 1 | import { IndexAccessContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { BaseNode } from '../base'; 3 | import { Expression } from './expression'; 4 | 5 | export class IndexAccess extends BaseNode { 6 | type = 'IndexAccess' as const; 7 | baseExpression: Expression; 8 | indexExpression: Expression | null = null; 9 | constructor(ctx: IndexAccessContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | this.baseExpression = ctx.expression(0)!.accept(visitor); 12 | this.indexExpression = ctx._index?.accept(visitor) ?? null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ast/expression/index-range-access.ts: -------------------------------------------------------------------------------- 1 | import { IndexRangeAccessContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { BaseNode } from '../base'; 3 | import { Expression } from './expression'; 4 | 5 | export class IndexRangeAccess extends BaseNode { 6 | type = 'IndexRangeAccess' as const; 7 | baseExpression: Expression; 8 | startExpression: Expression | null = null; 9 | endExpression: Expression | null = null; 10 | constructor(ctx: IndexRangeAccessContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.baseExpression = ctx.expression(0)!.accept(visitor); 13 | this.startExpression = ctx._startIndex?.accept(visitor) ?? null; 14 | this.endExpression = ctx._endIndex?.accept(visitor) ?? null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ast/expression/index.node.ts: -------------------------------------------------------------------------------- 1 | import { AssignOp } from './assign-op'; 2 | import { Assignment } from './assignment'; 3 | import { BinaryOperation } from './binary-operation'; 4 | import { BooleanLiteral } from './boolean-literal'; 5 | import { CallArgumentList } from './call-argument-list'; 6 | import { Conditional } from './conditional'; 7 | import { FunctionCallOptions } from './function-call-options'; 8 | import { FunctionCall } from './function-call'; 9 | import { HexStringLiteral } from './hex-string-literal'; 10 | import { Identifier } from './identifier'; 11 | import { IndexAccess } from './index-access'; 12 | import { IndexRangeAccess } from './index-range-access'; 13 | import { InlineArray } from './inline-array'; 14 | import { MemberAccess } from './member-access'; 15 | import { MetaType } from './meta-type'; 16 | import { NamedArgument } from './named-argument'; 17 | import { NewExpr } from './new-expr'; 18 | import { NumberLiteral } from './number-literal'; 19 | import { PayableConversion } from './payable-conversion'; 20 | import { StringLiteral } from './string-literal'; 21 | import { TupleExpression } from './tuple-expression'; 22 | import { UnaryOperation } from './unary-operation'; 23 | import { UnicodeStringLiteral } from './unicode-string-literal'; 24 | import { UserDefinableOperator } from './user-definable-operator'; 25 | 26 | export { 27 | AssignOp, 28 | Assignment, 29 | BinaryOperation, 30 | BooleanLiteral, 31 | CallArgumentList, 32 | Conditional, 33 | FunctionCallOptions, 34 | FunctionCall, 35 | HexStringLiteral, 36 | Identifier, 37 | IndexAccess, 38 | IndexRangeAccess, 39 | InlineArray, 40 | MemberAccess, 41 | MetaType, 42 | NamedArgument, 43 | NewExpr, 44 | NumberLiteral, 45 | PayableConversion, 46 | StringLiteral, 47 | TupleExpression, 48 | UnaryOperation, 49 | UnicodeStringLiteral, 50 | UserDefinableOperator, 51 | }; 52 | -------------------------------------------------------------------------------- /src/ast/expression/index.ts: -------------------------------------------------------------------------------- 1 | import * as nodeMap from './index.node'; 2 | import { UnionSyntaxNode, UnionSyntaxNodeType } from '../base'; 3 | 4 | export type ExpressionNode = UnionSyntaxNode; 5 | export type ExpressionNodeType = UnionSyntaxNodeType; 6 | 7 | export const expressionNodeTypes = Object.keys(nodeMap) as ExpressionNodeType[]; 8 | export * from './assign-op'; 9 | export * from './assignment'; 10 | export * from './binary-operation'; 11 | export * from './boolean-literal'; 12 | export * from './call-argument-list'; 13 | export * from './conditional'; 14 | export * from './expression'; 15 | export * from './function-call-options'; 16 | export * from './function-call'; 17 | export * from './hex-string-literal'; 18 | export * from './identifier'; 19 | export * from './index-access'; 20 | export * from './index-range-access'; 21 | export * from './inline-array-expression'; 22 | export * from './inline-array'; 23 | export * from './literal'; 24 | export * from './member-access'; 25 | export * from './meta-type'; 26 | export * from './named-argument'; 27 | export * from './new-expr'; 28 | export * from './number-literal'; 29 | export * from './payable-conversion'; 30 | export * from './primary-expression'; 31 | export * from './string-literal'; 32 | export * from './tuple-expression'; 33 | export * from './tuple'; 34 | export * from './unary-operation'; 35 | export * from './unicode-string-literal'; 36 | export * from './user-definable-operator'; 37 | -------------------------------------------------------------------------------- /src/ast/expression/inline-array-expression.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList } from '../base'; 2 | import { InlineArrayExpressionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from './expression'; 4 | 5 | export type InlineArrayExpression = Expression[]; 6 | 7 | export const InlineArrayExpression = class extends BaseNodeList { 8 | type = 'InlineArrayExpression' as const; 9 | constructor(ctx: InlineArrayExpressionContext, visitor: SolidityParserVisitor) { 10 | super(ctx.expression(), visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/expression/inline-array.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { InlineArrayContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { InlineArrayExpression } from './inline-array-expression'; 4 | 5 | export class InlineArray extends BaseNode { 6 | type = 'InlineArray' as const; 7 | expressions: InlineArrayExpression = []; 8 | constructor(ctx: InlineArrayContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.expressions = ctx.inlineArrayExpression().accept(visitor); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/expression/literal.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeUnion } from '../base'; 2 | import { LiteralContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { StringLiteral } from './string-literal'; 4 | import { NumberLiteral } from './number-literal'; 5 | import { BooleanLiteral } from './boolean-literal'; 6 | import { HexStringLiteral } from './hex-string-literal'; 7 | import { UnicodeStringLiteral } from './unicode-string-literal'; 8 | 9 | export type Literal = 10 | | StringLiteral 11 | | NumberLiteral 12 | | BooleanLiteral 13 | | HexStringLiteral 14 | | UnicodeStringLiteral; 15 | 16 | export const Literal = class extends BaseNodeUnion< 17 | StringLiteral | NumberLiteral | BooleanLiteral | HexStringLiteral | UnicodeStringLiteral 18 | > { 19 | // type = 'Literal' as const; 20 | constructor(ctx: LiteralContext, visitor: SolidityParserVisitor) { 21 | super( 22 | ctx, 23 | [ 24 | ctx.stringLiteral(), 25 | ctx.numberLiteral(), 26 | ctx.booleanLiteral(), 27 | ctx.hexStringLiteral(), 28 | ctx.unicodeStringLiteral(), 29 | ], 30 | visitor, 31 | ); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/ast/expression/member-access.ts: -------------------------------------------------------------------------------- 1 | import { MemberAccessContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { BaseNode } from '../base'; 3 | import { Expression } from './expression'; 4 | 5 | export class MemberAccess extends BaseNode { 6 | type = 'MemberAccess' as const; 7 | expression: Expression; 8 | memberName: string | null = null; 9 | 10 | constructor(ctx: MemberAccessContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.expression = ctx.expression().accept(visitor); 13 | if (ctx.Address()) { 14 | this.memberName = 'address'; 15 | } else if (!!ctx.identifier()) { 16 | this.memberName = ctx.identifier()!.getText(); 17 | } else { 18 | this.memberName = null; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ast/expression/meta-type.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { MetaTypeContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { TypeName } from '../type'; 4 | 5 | // `type(TypeName)` such as type(address) 6 | export class MetaType extends BaseNode { 7 | type = 'MetaType' as const; 8 | typeName: TypeName; 9 | constructor(ctx: MetaTypeContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | this.typeName = ctx.typeName().accept(visitor); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ast/expression/named-argument.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { NamedArgumentContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from './identifier'; 4 | import { Expression } from './expression'; 5 | 6 | export class NamedArgument extends BaseNode { 7 | type = 'NamedArgument' as const; 8 | name: Identifier; 9 | expression: Expression; 10 | constructor(ctx: NamedArgumentContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.name = ctx.identifier().accept(visitor); 13 | this.expression = ctx.expression().accept(visitor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/expression/new-expr.ts: -------------------------------------------------------------------------------- 1 | import { NewExprContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { BaseNode } from '../base'; 3 | import { TypeName } from '../type'; 4 | 5 | export class NewExpr extends BaseNode { 6 | type = 'NewExpr' as const; 7 | typeName: TypeName; 8 | constructor(ctx: NewExprContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.typeName = ctx.typeName().accept(visitor); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/expression/number-literal.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { 3 | LiteralWithSubDenominationContext, 4 | NumberLiteralContext, 5 | SolidityParserVisitor, 6 | } from '../../antlr4'; 7 | 8 | type EtherUnit = 'wei' | 'gwei' | 'szabo' | 'finney' | 'ether'; 9 | type TimeUnit = 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'; 10 | 11 | export class NumberLiteral extends BaseNode { 12 | type = 'NumberLiteral' as const; 13 | value: string | null = null; 14 | hexValue: string | null = null; 15 | subDenomination: EtherUnit | TimeUnit | null = null; 16 | constructor( 17 | ctx: NumberLiteralContext | LiteralWithSubDenominationContext, 18 | visitor: SolidityParserVisitor, 19 | ) { 20 | super(ctx, visitor); 21 | 22 | let target: NumberLiteralContext; 23 | 24 | if (ctx instanceof LiteralWithSubDenominationContext) { 25 | target = ctx.numberLiteral(); 26 | this.subDenomination = (ctx.SubDenomination()?.getText() as any) ?? null; 27 | } else { 28 | target = ctx; 29 | } 30 | 31 | this.value = target.DecimalNumber()?.getText() ?? null; 32 | this.hexValue = target.HexNumber()?.getText() ?? null; 33 | } 34 | } 35 | 36 | export { NumberLiteral as LiteralWithSubDenomination }; 37 | -------------------------------------------------------------------------------- /src/ast/expression/payable-conversion.ts: -------------------------------------------------------------------------------- 1 | import { PayableConversionContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { BaseNode } from '../base'; 3 | import { CallArgumentList } from './call-argument-list'; 4 | 5 | export class PayableConversion extends BaseNode { 6 | type = 'PayableConversion' as const; 7 | arguments: CallArgumentList | null = null; 8 | constructor(ctx: PayableConversionContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.arguments = ctx.callArgumentList().accept(visitor); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/expression/primary-expression.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeUnion } from '../base'; 2 | import { PrimaryExpressionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { LiteralWithSubDenomination } from './number-literal'; 4 | import { Literal } from './literal'; 5 | import { ElementaryTypeName } from '../type'; 6 | import { Identifier } from './identifier'; 7 | 8 | export type PrimaryExpression = 9 | | LiteralWithSubDenomination 10 | | Literal 11 | | ElementaryTypeName 12 | | Identifier; 13 | 14 | export const PrimaryExpression = class extends BaseNodeUnion< 15 | LiteralWithSubDenomination | Literal | ElementaryTypeName | Identifier 16 | > { 17 | // type = 'PrimaryExpression' as const; 18 | constructor(ctx: PrimaryExpressionContext, visitor: SolidityParserVisitor) { 19 | super( 20 | ctx, 21 | [ctx.literalWithSubDenomination(), ctx.literal(), ctx.elementaryTypeName(), ctx.identifier()], 22 | visitor, 23 | ); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/ast/expression/string-literal.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { StringLiteralContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class StringLiteral extends BaseNode { 5 | type = 'StringLiteral' as const; 6 | value: string; 7 | constructor(ctx: StringLiteralContext, visitor: SolidityParserVisitor) { 8 | super(ctx, visitor); 9 | const nameString = ctx.NonEmptyStringLiteral(0)?.getText(); 10 | this.value = nameString ? nameString.substring(1, nameString.length - 1) : ''; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/expression/tuple-expression.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { TupleExpressionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from './expression'; 4 | 5 | export class TupleExpression extends BaseNode { 6 | type = 'TupleExpression' as const; 7 | expressions: Expression[] = []; 8 | constructor(ctx: TupleExpressionContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.expressions = ctx.expression().map((exp) => exp.accept(visitor)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/expression/tuple.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeUnion } from '../base'; 2 | import { TupleContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { TupleExpression } from './tuple-expression'; 4 | 5 | export type Tuple = TupleExpression; 6 | 7 | export const Tuple = class extends BaseNodeUnion { 8 | // type = 'Tuple' as const; 9 | constructor(ctx: TupleContext, visitor: SolidityParserVisitor) { 10 | super(ctx, [ctx.tupleExpression()], visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/expression/unary-operation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UnaryPrefixOperationContext, 3 | SolidityParserVisitor, 4 | UnarySuffixOperationContext, 5 | } from '../../antlr4'; 6 | import { BaseNode } from '../base'; 7 | import { Expression } from './expression'; 8 | 9 | export class UnaryOperation extends BaseNode { 10 | type = 'UnaryOperation' as const; 11 | operator: string | null = null; 12 | left: Expression | null = null; 13 | right: Expression | null = null; 14 | constructor( 15 | ctx: UnaryPrefixOperationContext | UnarySuffixOperationContext, 16 | visitor: SolidityParserVisitor, 17 | ) { 18 | super(ctx, visitor); 19 | 20 | if (ctx instanceof UnaryPrefixOperationContext) { 21 | this.operator = ctx.getChild(0)?.getText() ?? null; 22 | this.right = ctx.getChild(1)?.accept(visitor) ?? null; 23 | } else if (ctx instanceof UnarySuffixOperationContext) { 24 | this.left = ctx.getChild(0)?.accept(visitor) ?? null; 25 | this.operator = ctx.getChild(1)?.getText() ?? null; 26 | } 27 | } 28 | } 29 | 30 | export { UnaryOperation as UnaryPrefixOperation, UnaryOperation as UnarySuffixOperation }; 31 | -------------------------------------------------------------------------------- /src/ast/expression/unicode-string-literal.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { UnicodeStringLiteralContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class UnicodeStringLiteral extends BaseNode { 5 | type = 'UnicodeStringLiteral' as const; 6 | value: string; 7 | constructor(ctx: UnicodeStringLiteralContext, visitor: SolidityParserVisitor) { 8 | super(ctx, visitor); 9 | const hexString = ctx.UnicodeStringLiteral(0)?.getText(); 10 | this.value = hexString ? hexString.substring(8, hexString.length - 1) : ''; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/expression/user-definable-operator.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { UserDefinableOperatorContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class UserDefinableOperator extends BaseNodeString { 5 | type = 'UserDefinableOperator' as const; 6 | constructor(ctx: UserDefinableOperatorContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ast/index.node.ts: -------------------------------------------------------------------------------- 1 | export * from './declaration/index.node'; 2 | export * from './expression/index.node'; 3 | export * from './meta/index.node'; 4 | export * from './statement/index.node'; 5 | export * from './type/index.node'; 6 | export * from './yul/index.node'; 7 | -------------------------------------------------------------------------------- /src/ast/index.ts: -------------------------------------------------------------------------------- 1 | import * as nodeMap from './index.node'; 2 | import { DeclarationNode } from './declaration'; 3 | import { ExpressionNode } from './expression'; 4 | import { MetaNode } from './meta'; 5 | import { StatementNode } from './statement'; 6 | import { TypeNode } from './type'; 7 | import { YulNode } from './yul'; 8 | 9 | export type SyntaxNode = 10 | | DeclarationNode 11 | | ExpressionNode 12 | | MetaNode 13 | | StatementNode 14 | | TypeNode 15 | | YulNode; 16 | 17 | export type SyntaxNodeType = SyntaxNode['type']; 18 | 19 | export const syntaxNodeTypes = Object.keys(nodeMap) as SyntaxNodeType[]; 20 | 21 | export * from './declaration'; 22 | export * from './expression'; 23 | export * from './meta'; 24 | export * from './statement'; 25 | export * from './type'; 26 | export * from './yul'; 27 | -------------------------------------------------------------------------------- /src/ast/meta/data-location.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { DataLocationContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | type DataLocationName = 'storage' | 'memory' | 'calldata'; 5 | 6 | export class DataLocation extends BaseNodeString { 7 | type = 'DataLocation' as const; 8 | name: DataLocationName; 9 | constructor(ctx: DataLocationContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | if (ctx.Storage()) { 12 | this.name = 'storage'; 13 | } else if (ctx.Memory()) { 14 | this.name = 'memory'; 15 | } else if (ctx.Calldata()) { 16 | this.name = 'calldata'; 17 | } else { 18 | throw new Error('unknown data location'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ast/meta/identifier-path.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { IdentifierPathContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | 5 | export class IdentifierPath extends BaseNodeString { 6 | type = 'IdentifierPath' as const; 7 | name: string; 8 | identifiers: Identifier[]; 9 | constructor(ctx: IdentifierPathContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | this.name = ctx.getText(); 12 | this.identifiers = ctx.identifier().map((identifier) => identifier.accept(visitor)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ast/meta/import-aliases.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ImportAliasesContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | 5 | export class ImportAliases extends BaseNode { 6 | type = 'ImportAliases' as const; 7 | foreign: Identifier; 8 | local: Identifier | null = null; 9 | constructor(ctx: ImportAliasesContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | this.foreign = ctx.identifier(0)!.accept(visitor); 12 | if (!!ctx.As()) { 13 | this.local = ctx.identifier(1)?.accept(visitor) ?? null; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ast/meta/import-directive.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ImportDirectiveContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | import { Path } from './path'; 5 | import { ImportAliases } from './import-aliases'; 6 | 7 | export class ImportDirective extends BaseNode { 8 | type = 'ImportDirective' as const; 9 | path: Path; 10 | importAll: boolean = false; // import * as Foo from 'foo'; 11 | unitAlias: Identifier | null = null; 12 | symbolAliases: ImportAliases[] = []; 13 | constructor(ctx: ImportDirectiveContext, visitor: SolidityParserVisitor) { 14 | super(ctx, visitor); 15 | this.path = ctx.path()!.accept(visitor); 16 | if (!!ctx.As() && ctx.identifier()) { 17 | this.unitAlias = ctx.identifier()!.accept(visitor); 18 | this.importAll = !!ctx.Mul(); 19 | } 20 | this.symbolAliases = ctx.symbolAliases()?.accept(visitor) || []; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ast/meta/index.node.ts: -------------------------------------------------------------------------------- 1 | import { DataLocation } from './data-location'; 2 | import { IdentifierPath } from './identifier-path'; 3 | import { ImportAliases } from './import-aliases'; 4 | import { ImportDirective } from './import-directive'; 5 | import { InheritanceSpecifier } from './inheritance-specifier'; 6 | import { ModifierInvocation } from './modifier-invocation'; 7 | import { Path } from './path'; 8 | import { PragmaDirective } from './pragma-directive'; 9 | import { SourceUnit } from './source-unit'; 10 | import { UsingAliases } from './using-aliases'; 11 | import { UsingDirective } from './using-directive'; 12 | 13 | export { 14 | DataLocation, 15 | IdentifierPath, 16 | ImportAliases, 17 | ImportDirective, 18 | InheritanceSpecifier, 19 | ModifierInvocation, 20 | Path, 21 | PragmaDirective, 22 | SourceUnit, 23 | UsingAliases, 24 | UsingDirective, 25 | }; 26 | -------------------------------------------------------------------------------- /src/ast/meta/index.ts: -------------------------------------------------------------------------------- 1 | import * as nodeMap from './index.node'; 2 | import { UnionSyntaxNode, UnionSyntaxNodeType } from '../base'; 3 | 4 | export type MetaNode = UnionSyntaxNode; 5 | export type MetaNodeType = UnionSyntaxNodeType; 6 | 7 | export const metaNodeTypes = Object.keys(nodeMap) as MetaNodeType[]; 8 | export * from './data-location'; 9 | export * from './identifier-path'; 10 | export * from './import-aliases'; 11 | export * from './import-directive'; 12 | export * from './inheritance-specifier-list'; 13 | export * from './inheritance-specifier'; 14 | export * from './modifier-invocation'; 15 | export * from './override-specifier'; 16 | export * from './parameter-list'; 17 | export * from './path'; 18 | export * from './pragma-directive'; 19 | export * from './source-unit'; 20 | export * from './symbol-aliases'; 21 | export * from './using-aliases'; 22 | export * from './using-directive'; 23 | -------------------------------------------------------------------------------- /src/ast/meta/inheritance-specifier-list.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList } from '../base'; 2 | import { InheritanceSpecifierListContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { InheritanceSpecifier } from './inheritance-specifier'; 4 | 5 | export type InheritanceSpecifierList = InheritanceSpecifier[]; 6 | 7 | export const InheritanceSpecifierList = class extends BaseNodeList { 8 | type = 'InheritanceSpecifierList' as const; 9 | constructor(ctx: InheritanceSpecifierListContext, visitor: SolidityParserVisitor) { 10 | super(ctx.inheritanceSpecifier(), visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/meta/inheritance-specifier.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { InheritanceSpecifierContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { IdentifierPath } from './identifier-path'; 4 | import { CallArgumentList } from '../expression'; 5 | 6 | export class InheritanceSpecifier extends BaseNode { 7 | type = 'InheritanceSpecifier' as const; 8 | baseName: IdentifierPath; 9 | arguments: CallArgumentList | null = null; 10 | constructor(ctx: InheritanceSpecifierContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.baseName = ctx.identifierPath().accept(visitor); 13 | this.arguments = ctx.callArgumentList()?.accept(visitor) ?? null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/meta/modifier-invocation.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ModifierInvocationContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { CallArgumentList } from '../expression'; 4 | import { IdentifierPath } from './identifier-path'; 5 | 6 | export class ModifierInvocation extends BaseNode { 7 | type = 'ModifierInvocation' as const; 8 | name: IdentifierPath; 9 | arguments: CallArgumentList | null = null; 10 | constructor(ctx: ModifierInvocationContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.name = ctx.identifierPath().accept(visitor); 13 | this.arguments = ctx.callArgumentList()?.accept(visitor) ?? null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/meta/override-specifier.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList } from '../base'; 2 | import { OverrideSpecifierContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { IdentifierPath } from './identifier-path'; 4 | 5 | export type OverrideSpecifier = IdentifierPath[]; 6 | 7 | export const OverrideSpecifier = class extends BaseNodeList { 8 | type = 'OverrideSpecifier' as const; 9 | constructor(ctx: OverrideSpecifierContext, visitor: SolidityParserVisitor) { 10 | super(ctx.identifierPath(), visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/meta/parameter-list.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList } from '../base'; 2 | import { ParameterListContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { ParameterDeclaration } from '../declaration'; 4 | 5 | export type ParameterList = ParameterDeclaration[]; 6 | 7 | export const ParameterList = class extends BaseNodeList { 8 | type = 'ParameterList' as const; 9 | constructor(ctx: ParameterListContext, visitor: SolidityParserVisitor) { 10 | super(ctx.parameterDeclaration(), visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/meta/path.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString, formatString } from '../base'; 2 | import { PathContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class Path extends BaseNodeString { 5 | type = 'Path' as const; 6 | constructor(ctx: PathContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | this.name = formatString(ctx.NonEmptyStringLiteral().getText()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ast/meta/pragma-directive.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { PragmaDirectiveContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class PragmaDirective extends BaseNode { 5 | type = 'PragmaDirective' as const; 6 | literals: string[] = []; 7 | 8 | constructor(ctx: PragmaDirectiveContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | ctx.PragmaToken().forEach((t) => { 11 | const literals = t.getText().split(' ').filter(Boolean); 12 | this.literals.push(...literals); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/meta/source-unit.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { SourceUnitContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { ImportDirective } from './import-directive'; 4 | import { PragmaDirective } from './pragma-directive'; 5 | import { UsingDirective } from './using-directive'; 6 | import { 7 | VariableDeclaration, 8 | UserDefinedValueTypeDefinition, 9 | StructDefinition, 10 | FunctionDefinition, 11 | ErrorDefinition, 12 | EnumDefinition, 13 | ContractDefinition, 14 | } from '../declaration'; 15 | 16 | type SourceUnitNodes = 17 | | ContractDefinition 18 | | EnumDefinition 19 | | ErrorDefinition 20 | | FunctionDefinition 21 | | ImportDirective 22 | | PragmaDirective 23 | | StructDefinition 24 | | UserDefinedValueTypeDefinition 25 | | UsingDirective 26 | | VariableDeclaration; 27 | 28 | export class SourceUnit extends BaseNode { 29 | type = 'SourceUnit' as const; 30 | nodes: SourceUnitNodes[] = []; 31 | constructor(ctx: SourceUnitContext, visitor: SolidityParserVisitor) { 32 | super(ctx, visitor); 33 | this.nodes = (ctx.children || []).map((child) => visitor.visit(child)!).filter(Boolean); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ast/meta/symbol-aliases.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList } from '../base'; 2 | import { SymbolAliasesContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { ImportAliases } from './import-aliases'; 4 | 5 | export type SymbolAliases = ImportAliases[]; 6 | 7 | export const SymbolAliases = class extends BaseNodeList { 8 | type = 'SymbolAliases' as const; 9 | constructor(ctx: SymbolAliasesContext, visitor: SolidityParserVisitor) { 10 | super(ctx.importAliases(), visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/meta/using-aliases.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { UsingAliasesContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { IdentifierPath } from './identifier-path'; 4 | import { UserDefinableOperator } from '../expression'; 5 | 6 | export class UsingAliases extends BaseNode { 7 | type = 'UsingAliases' as const; 8 | name: IdentifierPath; 9 | operator: UserDefinableOperator | null = null; 10 | constructor(ctx: UsingAliasesContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.name = ctx.identifierPath().accept(visitor); 13 | this.operator = ctx.userDefinableOperator()?.accept(visitor) ?? null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/meta/using-directive.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { UsingDirectiveContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { UserDefinedValueTypeDefinition } from '../declaration'; 4 | import { IdentifierPath } from './identifier-path'; 5 | import { TypeName } from '../type'; 6 | import { UsingAliases } from './using-aliases'; 7 | 8 | export class UsingDirective extends BaseNode { 9 | type = 'UsingDirective' as const; 10 | global: boolean; 11 | libraryName: UserDefinedValueTypeDefinition | IdentifierPath | null = null; 12 | usingAliases: UsingAliases[] | null = null; 13 | typeName: TypeName | '*'; 14 | constructor(ctx: UsingDirectiveContext, visitor: SolidityParserVisitor) { 15 | super(ctx, visitor); 16 | this.global = !!ctx.Global(); 17 | this.typeName = !!ctx.Mul() ? '*' : ctx.typeName()?.accept(visitor); 18 | 19 | if (ctx.identifierPath() && !ctx.usingAliases().length) { 20 | this.libraryName = ctx.identifierPath()!.accept(visitor); 21 | } else { 22 | this.usingAliases = ctx.usingAliases().map((usingAlias) => usingAlias.accept(visitor)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ast/statement/assembly-flags.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeList, formatString } from '../base'; 2 | import { AssemblyFlagsContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export type AssemblyFlags = string[]; 5 | 6 | export const AssemblyFlags = class extends BaseNodeList { 7 | type = 'AssemblyFlags' as const; 8 | constructor(ctx: AssemblyFlagsContext, _visitor: SolidityParserVisitor) { 9 | super([], _visitor); 10 | // @ts-expect-error 11 | return ctx.AssemblyFlagString().map((x) => formatString(x.getText())); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/ast/statement/assembly-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode, formatString } from '../base'; 2 | import { AssemblyStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulStatement } from '../yul'; 4 | 5 | export class AssemblyStatement extends BaseNode { 6 | type = 'AssemblyStatement' as const; 7 | flags: string[] | null = null; 8 | dialect: 'evmasm' | string | null = null; 9 | yulStatements: YulStatement[] = []; 10 | constructor(ctx: AssemblyStatementContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.flags = ctx.assemblyFlags()?.accept(visitor) ?? null; 13 | this.dialect = ctx.AssemblyDialect() ? formatString(ctx.AssemblyDialect()!.getText()) : null; 14 | this.yulStatements = ctx.yulStatement().map((yulStatement) => yulStatement.accept(visitor)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ast/statement/block.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { BlockContext, SolidityParserVisitor, UncheckedBlockContext } from '../../antlr4'; 3 | import { Statement } from './statement'; 4 | 5 | export class Block extends BaseNode { 6 | type = 'Block' as const; 7 | unchecked: boolean = false; 8 | statements: Statement[] = []; 9 | constructor(_ctx: BlockContext | UncheckedBlockContext, visitor: SolidityParserVisitor) { 10 | super(_ctx, visitor); 11 | this.unchecked = _ctx instanceof UncheckedBlockContext; 12 | const ctx = _ctx instanceof UncheckedBlockContext ? _ctx.block() : _ctx; 13 | 14 | // because it starts with `{` and ends with `}` we need to skip those 15 | const statementsCount = ctx.getChildCount() >= 2 ? ctx.getChildCount() : 2; 16 | 17 | for (let index = 1; index < statementsCount - 1; index += 1) { 18 | const statement = ctx.getChild(index); 19 | if (!statement) return; 20 | this.statements.push(statement.accept(visitor)); 21 | } 22 | } 23 | } 24 | 25 | export { Block as UncheckedBlock }; 26 | -------------------------------------------------------------------------------- /src/ast/statement/break-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { BreakStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class BreakStatement extends BaseNodeString { 5 | type = 'BreakStatement' as const; 6 | constructor(ctx: BreakStatementContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | this.name = 'break'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ast/statement/catch-clause.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { CatchClauseContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | import { ParameterList } from '../meta'; 5 | import { Block } from './block'; 6 | 7 | export class CatchClause extends BaseNode { 8 | type = 'CatchClause' as const; 9 | errorName: Identifier | null = null; 10 | parameters: ParameterList | null = null; 11 | body: Block; 12 | 13 | constructor(ctx: CatchClauseContext, visitor: SolidityParserVisitor) { 14 | super(ctx, visitor); 15 | this.errorName = ctx.identifier()?.accept(visitor) ?? null; 16 | this.body = ctx.block().accept(visitor); 17 | this.parameters = ctx.parameterList()?.accept(visitor) ?? null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ast/statement/continue-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { ContinueStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class ContinueStatement extends BaseNodeString { 5 | type = 'ContinueStatement' as const; 6 | constructor(ctx: ContinueStatementContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | this.name = 'continue'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ast/statement/do-while-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { DoWhileStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from '../expression'; 4 | import { Statement } from './statement'; 5 | 6 | export class DoWhileStatement extends BaseNode { 7 | type = 'DoWhileStatement' as const; 8 | condition: Expression; 9 | body: Statement; 10 | constructor(ctx: DoWhileStatementContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.condition = ctx.expression().accept(visitor); 13 | this.body = ctx.statement().accept(visitor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/statement/emit-statement.ts: -------------------------------------------------------------------------------- 1 | import { EmitStatementContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { AbstractFunctionCall } from '../expression'; 3 | 4 | export class EmitStatement extends AbstractFunctionCall { 5 | type = 'EmitStatement' as const; 6 | constructor(ctx: EmitStatementContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ast/statement/expression-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ExpressionStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from '../expression/expression'; 4 | 5 | export class ExpressionStatement extends BaseNode { 6 | type = 'ExpressionStatement' as const; 7 | expression: Expression; 8 | constructor(ctx: ExpressionStatementContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.expression = ctx.expression().accept(visitor); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/ast/statement/for-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ForStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from '../expression'; 4 | import { ExpressionStatement } from './expression-statement'; 5 | import { Statement } from './statement'; 6 | import { SimpleStatement } from './simple-statement'; 7 | 8 | export class ForStatement extends BaseNode { 9 | type = 'ForStatement' as const; 10 | initializationExpression: SimpleStatement | null = null; 11 | conditionExpression: ExpressionStatement | null = null; 12 | loopExpression: Expression | null = null; 13 | body: Statement; 14 | constructor(ctx: ForStatementContext, visitor: SolidityParserVisitor) { 15 | super(ctx, visitor); 16 | this.initializationExpression = ctx.simpleStatement()?.accept(visitor) ?? null; 17 | this.conditionExpression = ctx.expressionStatement()?.accept(visitor) ?? null; 18 | this.loopExpression = ctx.expression()?.accept(visitor) ?? null; 19 | this.body = ctx.statement().accept(visitor); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ast/statement/if-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { IfStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from '../expression'; 4 | import { Statement } from './statement'; 5 | 6 | export class IfStatement extends BaseNode { 7 | type = 'IfStatement' as const; 8 | condition: Expression; 9 | ifStatement: Statement; 10 | elseStatement: Statement | null = null; 11 | constructor(ctx: IfStatementContext, visitor: SolidityParserVisitor) { 12 | super(ctx, visitor); 13 | this.condition = ctx.expression().accept(visitor); 14 | this.ifStatement = ctx.statement(0)!.accept(visitor); 15 | if (ctx.Else() && ctx.statement(1)) { 16 | this.elseStatement = ctx.statement(1)!.accept(visitor); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ast/statement/index.node.ts: -------------------------------------------------------------------------------- 1 | import { AssemblyStatement } from './assembly-statement'; 2 | import { Block } from './block'; 3 | import { BreakStatement } from './break-statement'; 4 | import { CatchClause } from './catch-clause'; 5 | import { ContinueStatement } from './continue-statement'; 6 | import { DoWhileStatement } from './do-while-statement'; 7 | import { EmitStatement } from './emit-statement'; 8 | import { ExpressionStatement } from './expression-statement'; 9 | import { ForStatement } from './for-statement'; 10 | import { IfStatement } from './if-statement'; 11 | import { ReturnStatement } from './return-statement'; 12 | import { RevertStatement } from './revert-statement'; 13 | import { TryStatement } from './try-statement'; 14 | import { VariableDeclarationStatement } from './variable-declaration-statement'; 15 | import { WhileStatement } from './while-statement'; 16 | 17 | export { 18 | AssemblyStatement, 19 | Block, 20 | BreakStatement, 21 | CatchClause, 22 | ContinueStatement, 23 | DoWhileStatement, 24 | EmitStatement, 25 | ExpressionStatement, 26 | ForStatement, 27 | IfStatement, 28 | ReturnStatement, 29 | RevertStatement, 30 | TryStatement, 31 | VariableDeclarationStatement, 32 | WhileStatement, 33 | }; 34 | -------------------------------------------------------------------------------- /src/ast/statement/index.ts: -------------------------------------------------------------------------------- 1 | import * as nodeMap from './index.node'; 2 | import { UnionSyntaxNode, UnionSyntaxNodeType } from '../base'; 3 | 4 | export type StatementNode = UnionSyntaxNode; 5 | export type StatementNodeType = UnionSyntaxNodeType; 6 | 7 | export const statementNodeTypes = Object.keys(nodeMap) as StatementNodeType[]; 8 | export * from './assembly-flags'; 9 | export * from './assembly-statement'; 10 | export * from './block'; 11 | export * from './break-statement'; 12 | export * from './catch-clause'; 13 | export * from './continue-statement'; 14 | export * from './do-while-statement'; 15 | export * from './emit-statement'; 16 | export * from './expression-statement'; 17 | export * from './for-statement'; 18 | export * from './if-statement'; 19 | export * from './return-statement'; 20 | export * from './revert-statement'; 21 | export * from './simple-statement'; 22 | export * from './statement'; 23 | export * from './try-statement'; 24 | export * from './variable-declaration-statement'; 25 | export * from './while-statement'; 26 | -------------------------------------------------------------------------------- /src/ast/statement/return-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { ReturnStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from '../expression'; 4 | 5 | export class ReturnStatement extends BaseNode { 6 | type = 'ReturnStatement' as const; 7 | expression: Expression | null = null; 8 | constructor(ctx: ReturnStatementContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.expression = ctx.expression()?.accept(visitor) ?? null; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/statement/revert-statement.ts: -------------------------------------------------------------------------------- 1 | import { RevertStatementContext, SolidityParserVisitor } from '../../antlr4'; 2 | import { AbstractFunctionCall } from '../expression'; 3 | 4 | export class RevertStatement extends AbstractFunctionCall { 5 | type = 'RevertStatement' as const; 6 | constructor(ctx: RevertStatementContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ast/statement/simple-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeUnion } from '../base'; 2 | import { SimpleStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { VariableDeclarationStatement } from './variable-declaration-statement'; 4 | import { ExpressionStatement } from './expression-statement'; 5 | 6 | export type SimpleStatement = VariableDeclarationStatement | ExpressionStatement; 7 | 8 | export const SimpleStatement = class extends BaseNodeUnion< 9 | VariableDeclarationStatement | ExpressionStatement 10 | > { 11 | // type = 'SimpleStatement' as const; 12 | constructor(ctx: SimpleStatementContext, visitor: SolidityParserVisitor) { 13 | super(ctx, [ctx.variableDeclarationStatement(), ctx.expressionStatement()], visitor); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/ast/statement/statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeUnion } from '../base'; 2 | import { StatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Block } from './block'; 4 | import { SimpleStatement } from './simple-statement'; 5 | import { IfStatement } from './if-statement'; 6 | import { ForStatement } from './for-statement'; 7 | import { WhileStatement } from './while-statement'; 8 | import { DoWhileStatement } from './do-while-statement'; 9 | import { ContinueStatement } from './continue-statement'; 10 | import { BreakStatement } from './break-statement'; 11 | import { TryStatement } from './try-statement'; 12 | import { ReturnStatement } from './return-statement'; 13 | import { EmitStatement } from './emit-statement'; 14 | import { RevertStatement } from './revert-statement'; 15 | import { AssemblyStatement } from './assembly-statement'; 16 | 17 | export type Statement = 18 | | Block 19 | | SimpleStatement 20 | | IfStatement 21 | | ForStatement 22 | | WhileStatement 23 | | DoWhileStatement 24 | | ContinueStatement 25 | | BreakStatement 26 | | TryStatement 27 | | ReturnStatement 28 | | EmitStatement 29 | | RevertStatement 30 | | AssemblyStatement; 31 | 32 | export const Statement = class extends BaseNodeUnion< 33 | | Block 34 | | SimpleStatement 35 | | IfStatement 36 | | ForStatement 37 | | WhileStatement 38 | | DoWhileStatement 39 | | ContinueStatement 40 | | BreakStatement 41 | | TryStatement 42 | | ReturnStatement 43 | | EmitStatement 44 | | RevertStatement 45 | | AssemblyStatement 46 | > { 47 | // type = 'Statement' as const; 48 | constructor(ctx: StatementContext, visitor: SolidityParserVisitor) { 49 | super( 50 | ctx, 51 | [ 52 | ctx.block(), 53 | ctx.simpleStatement(), 54 | ctx.ifStatement(), 55 | ctx.forStatement(), 56 | ctx.whileStatement(), 57 | ctx.doWhileStatement(), 58 | ctx.continueStatement(), 59 | ctx.breakStatement(), 60 | ctx.tryStatement(), 61 | ctx.returnStatement(), 62 | ctx.emitStatement(), 63 | ctx.revertStatement(), 64 | ctx.assemblyStatement(), 65 | ], 66 | visitor, 67 | ); 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /src/ast/statement/try-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { TryStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from '../expression'; 4 | import { ParameterList } from '../meta'; 5 | import { Block } from './block'; 6 | import { CatchClause } from './catch-clause'; 7 | 8 | export class TryStatement extends BaseNode { 9 | type = 'TryStatement' as const; 10 | expression: Expression; 11 | returnParameters: ParameterList | null = null; 12 | body: Block; 13 | catchClauses: CatchClause[]; 14 | constructor(ctx: TryStatementContext, visitor: SolidityParserVisitor) { 15 | super(ctx, visitor); 16 | this.expression = ctx.expression().accept(visitor); 17 | this.returnParameters = ctx.parameterList()?.accept(visitor) ?? null; 18 | this.body = ctx.block().accept(visitor); 19 | this.catchClauses = ctx.catchClause().map((x) => x.accept(visitor)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ast/statement/variable-declaration-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { VariableDeclarationStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { VariableDeclaration, VariableDeclarationTuple } from '../declaration'; 4 | import { Expression } from '../expression'; 5 | 6 | export class VariableDeclarationStatement extends BaseNode { 7 | type = 'VariableDeclarationStatement' as const; 8 | variable: VariableDeclaration | null = null; 9 | variableTuple: VariableDeclarationTuple | null = null; 10 | expression: Expression | null = null; 11 | 12 | constructor(ctx: VariableDeclarationStatementContext, visitor: SolidityParserVisitor) { 13 | super(ctx, visitor); 14 | this.variableTuple = ctx.variableDeclarationTuple()?.accept(visitor) ?? null; 15 | this.variable = ctx.variableDeclaration()?.accept(visitor) ?? null; 16 | this.expression = ctx.expression()?.accept(visitor) ?? null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ast/statement/while-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { WhileStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Statement } from './statement'; 4 | import { Expression } from '../expression'; 5 | 6 | export class WhileStatement extends BaseNode { 7 | type = 'WhileStatement' as const; 8 | condition: Expression; 9 | body: Statement; 10 | constructor(ctx: WhileStatementContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.condition = ctx.expression().accept(visitor); 13 | this.body = ctx.statement().accept(visitor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/type/elementary-type-name.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { ElementaryTypeNameContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class ElementaryTypeName extends BaseNodeString { 5 | type = 'ElementaryTypeName' as const; 6 | name: string; 7 | payable: boolean = false; 8 | constructor(ctx: ElementaryTypeNameContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.payable = !!ctx.Payable(); 11 | 12 | const types = [ 13 | ctx.Address(), 14 | ctx.Bool(), 15 | ctx.String(), 16 | ctx.Bytes(), 17 | ctx.SignedIntegerType(), 18 | ctx.UnsignedIntegerType(), 19 | ctx.FixedBytes(), 20 | ctx.Fixed(), 21 | ctx.Ufixed(), 22 | ]; 23 | this.name = types.find((t) => !!t)?.getText() ?? ctx.getText(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ast/type/function-type-name.ts: -------------------------------------------------------------------------------- 1 | import { BaseFunctionDefinition } from '../declaration/function-definition'; 2 | 3 | export class FunctionTypeName extends BaseFunctionDefinition { 4 | type = 'FunctionTypeName' as const; 5 | } 6 | -------------------------------------------------------------------------------- /src/ast/type/index.node.ts: -------------------------------------------------------------------------------- 1 | import { ElementaryTypeName } from './elementary-type-name'; 2 | import { FunctionTypeName } from './function-type-name'; 3 | import { MappingKeyType } from './mapping-key-type'; 4 | import { MappingType } from './mapping-type'; 5 | import { TypeName } from './type-name'; 6 | 7 | export { ElementaryTypeName, FunctionTypeName, MappingKeyType, MappingType, TypeName }; 8 | -------------------------------------------------------------------------------- /src/ast/type/index.ts: -------------------------------------------------------------------------------- 1 | import * as nodeMap from './index.node'; 2 | import { UnionSyntaxNode, UnionSyntaxNodeType } from '../base'; 3 | 4 | export type TypeNode = UnionSyntaxNode; 5 | export type TypeNodeType = UnionSyntaxNodeType; 6 | 7 | export const typeNodeTypes = Object.keys(nodeMap) as TypeNodeType[]; 8 | export * from './elementary-type-name'; 9 | export * from './function-type-name'; 10 | export * from './mapping-key-type'; 11 | export * from './mapping-type'; 12 | export * from './type-name'; 13 | -------------------------------------------------------------------------------- /src/ast/type/mapping-key-type.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { MappingKeyTypeContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class MappingKeyType extends BaseNodeString { 5 | type = 'MappingKeyType' as const; 6 | constructor(ctx: MappingKeyTypeContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ast/type/mapping-type.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { MappingTypeContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Identifier } from '../expression'; 4 | import { MappingKeyType } from './mapping-key-type'; 5 | import { TypeName } from './type-name'; 6 | 7 | export class MappingType extends BaseNode { 8 | type = 'MappingType' as const; 9 | name: Identifier | null = null; 10 | keyType: MappingKeyType | null = null; 11 | valueType: TypeName | null = null; 12 | constructor(ctx: MappingTypeContext, visitor: SolidityParserVisitor) { 13 | super(ctx, visitor); 14 | this.name = ctx._name?.accept(visitor) ?? null; 15 | this.keyType = ctx._key?.accept(visitor) ?? null; 16 | this.valueType = ctx._value?.accept(visitor) ?? null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ast/type/type-name.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { TypeNameContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { Expression } from '../expression'; 4 | import { ElementaryTypeName } from './elementary-type-name'; 5 | import { FunctionTypeName } from './function-type-name'; 6 | import { MappingType } from './mapping-type'; 7 | import { IdentifierPath } from '../meta'; 8 | 9 | class TypeNameWithArray extends BaseNodeString { 10 | type = 'TypeName' as const; 11 | baseType: ElementaryTypeName | FunctionTypeName | MappingType | IdentifierPath; 12 | expression: Expression | null; 13 | 14 | constructor(ctx: TypeNameContext, visitor: SolidityParserVisitor) { 15 | super(ctx, visitor); 16 | 17 | const target = [ 18 | ctx.elementaryTypeName(), 19 | ctx.functionTypeName(), 20 | ctx.mappingType(), 21 | ctx.identifierPath(), 22 | ctx.typeName(), 23 | ].find(Boolean); 24 | 25 | if (!ctx.LBrack()) { 26 | return target!.accept(visitor); 27 | } 28 | 29 | this.name = ctx.getText(); 30 | this.baseType = target!.accept(visitor); 31 | this.expression = ctx.expression()?.accept(visitor) ?? null; 32 | } 33 | } 34 | 35 | export type TypeName = 36 | | ElementaryTypeName 37 | | FunctionTypeName 38 | | MappingType 39 | | IdentifierPath 40 | | TypeNameWithArray; 41 | 42 | export const TypeName = TypeNameWithArray; 43 | -------------------------------------------------------------------------------- /src/ast/yul/index.node.ts: -------------------------------------------------------------------------------- 1 | import { YulAssignment } from './yul-assignment'; 2 | import { YulBlock } from './yul-block'; 3 | import { YulBoolean } from './yul-boolean'; 4 | import { YulForStatement } from './yul-for-statement'; 5 | import { YulFunctionCall } from './yul-function-call'; 6 | import { YulFunctionDefinition } from './yul-function-definition'; 7 | import { YulIfStatement } from './yul-if-statement'; 8 | import { YulLiteral } from './yul-literal'; 9 | import { YulPath } from './yul-path'; 10 | import { YulStatement } from './yul-statement'; 11 | import { YulSwitchCase } from './yul-switch-case'; 12 | import { YulSwitchStatement } from './yul-switch-statement'; 13 | import { YulVariableDeclaration } from './yul-variable-declaration'; 14 | 15 | export { 16 | YulAssignment, 17 | YulBlock, 18 | YulBoolean, 19 | YulForStatement, 20 | YulFunctionCall, 21 | YulFunctionDefinition, 22 | YulIfStatement, 23 | YulLiteral, 24 | YulPath, 25 | YulStatement, 26 | YulSwitchCase, 27 | YulSwitchStatement, 28 | YulVariableDeclaration, 29 | }; 30 | -------------------------------------------------------------------------------- /src/ast/yul/index.ts: -------------------------------------------------------------------------------- 1 | import * as nodeMap from './index.node'; 2 | import { UnionSyntaxNode, UnionSyntaxNodeType } from '../base'; 3 | 4 | export type YulNode = UnionSyntaxNode; 5 | export type YulNodeType = UnionSyntaxNodeType; 6 | 7 | export const yulNodeTypes = Object.keys(nodeMap) as YulNodeType[]; 8 | export * from './yul-assignment'; 9 | export * from './yul-block'; 10 | export * from './yul-boolean'; 11 | export * from './yul-expression'; 12 | export * from './yul-for-statement'; 13 | export * from './yul-function-call'; 14 | export * from './yul-function-definition'; 15 | export * from './yul-if-statement'; 16 | export * from './yul-literal'; 17 | export * from './yul-path'; 18 | export * from './yul-statement'; 19 | export * from './yul-switch-case'; 20 | export * from './yul-switch-statement'; 21 | export * from './yul-variable-declaration'; 22 | -------------------------------------------------------------------------------- /src/ast/yul/yul-assignment.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulAssignmentContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulPath } from './yul-path'; 4 | import { YulExpression } from './yul-expression'; 5 | import { YulFunctionCall } from './yul-function-call'; 6 | 7 | export class YulAssignment extends BaseNode { 8 | type = 'YulAssignment' as const; 9 | paths: YulPath[] = []; 10 | expression: YulExpression | YulFunctionCall; 11 | constructor(ctx: YulAssignmentContext, visitor: SolidityParserVisitor) { 12 | super(ctx, visitor); 13 | this.paths = ctx.yulPath()?.map((p) => p.accept(visitor)) ?? []; 14 | this.expression = (ctx.yulFunctionCall() || ctx.yulExpression())!.accept(visitor); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ast/yul/yul-block.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulBlockContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulStatement } from './yul-statement'; 4 | 5 | export class YulBlock extends BaseNode { 6 | type = 'YulBlock' as const; 7 | statements: YulStatement[] = []; 8 | constructor(ctx: YulBlockContext, visitor: SolidityParserVisitor) { 9 | super(ctx, visitor); 10 | this.statements = ctx.yulStatement().map((s) => s.accept(visitor)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/yul/yul-boolean.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { YulBooleanContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class YulBoolean extends BaseNodeString { 5 | type = 'YulBoolean' as const; 6 | constructor(ctx: YulBooleanContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | this.name = ctx.YulTrue() ? 'true' : 'false'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ast/yul/yul-expression.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeUnion } from '../base'; 2 | import { YulExpressionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulFunctionCall } from './yul-function-call'; 4 | import { YulPath } from './yul-path'; 5 | import { YulLiteral } from './yul-literal'; 6 | 7 | export type YulExpression = YulFunctionCall | YulPath | YulLiteral; 8 | 9 | export const YulExpression = class extends BaseNodeUnion { 10 | // type = 'YulExpression' as const; 11 | constructor(ctx: YulExpressionContext, visitor: SolidityParserVisitor) { 12 | super(ctx, [ctx.yulPath(), ctx.yulFunctionCall(), ctx.yulLiteral()], visitor); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/ast/yul/yul-for-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulForStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulExpression } from './yul-expression'; 4 | import { YulBlock } from './yul-block'; 5 | 6 | export class YulForStatement extends BaseNode { 7 | type = 'YulForStatement' as const; 8 | initializationBlock: YulBlock | null = null; 9 | conditionExpression: YulExpression | null = null; 10 | loopBlock: YulBlock | null = null; 11 | body: YulBlock | null = null; 12 | constructor(ctx: YulForStatementContext, visitor: SolidityParserVisitor) { 13 | super(ctx, visitor); 14 | this.initializationBlock = ctx._init?.accept(visitor) ?? null; 15 | this.conditionExpression = ctx._cond?.accept(visitor) ?? null; 16 | this.loopBlock = ctx._post?.accept(visitor) ?? null; 17 | this.body = ctx._body?.accept(visitor) ?? null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ast/yul/yul-function-call.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulFunctionCallContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulExpression } from './yul-expression'; 4 | 5 | type YulEVMBuiltIn = 6 | | 'stop' 7 | | 'add' 8 | | 'sub' 9 | | 'mul' 10 | | 'div' 11 | | 'sdiv' 12 | | 'mod' 13 | | 'smod' 14 | | 'exp' 15 | | 'not' 16 | | 'lt' 17 | | 'gt' 18 | | 'slt' 19 | | 'sgt' 20 | | 'eq' 21 | | 'iszero' 22 | | 'and' 23 | | 'or' 24 | | 'xor' 25 | | 'byte' 26 | | 'shl' 27 | | 'shr' 28 | | 'sar' 29 | | 'addmod' 30 | | 'mulmod' 31 | | 'signextend' 32 | | 'keccak256' 33 | | 'pop' 34 | | 'mload' 35 | | 'mstore' 36 | | 'mstore8' 37 | | 'sload' 38 | | 'sstore' 39 | | 'msize' 40 | | 'gas' 41 | | 'address' 42 | | 'balance' 43 | | 'selfbalance' 44 | | 'caller' 45 | | 'callvalue' 46 | | 'calldataload' 47 | | 'calldatasize' 48 | | 'calldatacopy' 49 | | 'extcodesize' 50 | | 'extcodecopy' 51 | | 'returndatasize' 52 | | 'returndatacopy' 53 | | 'extcodehash' 54 | | 'create' 55 | | 'create2' 56 | | 'call' 57 | | 'callcode' 58 | | 'delegatecall' 59 | | 'staticcall' 60 | | 'return' 61 | | 'revert' 62 | | 'selfdestruct' 63 | | 'invalid' 64 | | 'log0' 65 | | 'log1' 66 | | 'log2' 67 | | 'log3' 68 | | 'log4' 69 | | 'chainid' 70 | | 'origin' 71 | | 'gasprice' 72 | | 'blockhash' 73 | | 'coinbase' 74 | | 'timestamp' 75 | | 'number' 76 | | 'difficulty' 77 | | 'prevrandao' 78 | | 'gaslimit' 79 | | 'basefee'; 80 | 81 | export class YulFunctionCall extends BaseNode { 82 | type = 'YulFunctionCall' as const; 83 | identifier: string | YulEVMBuiltIn; 84 | expressions: YulExpression[] = []; 85 | constructor(ctx: YulFunctionCallContext, visitor: SolidityParserVisitor) { 86 | super(ctx, visitor); 87 | this.identifier = (ctx.YulEVMBuiltin() || ctx.YulIdentifier())!.getText(); 88 | this.expressions = ctx.yulExpression().map((e) => e.accept(visitor)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ast/yul/yul-function-definition.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulFunctionDefinitionContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulBlock } from './yul-block'; 4 | 5 | export class YulFunctionDefinition extends BaseNode { 6 | type = 'YulFunctionDefinition' as const; 7 | name: string; 8 | parameters: string[] = []; 9 | returnParameters: string[] = []; 10 | body: YulBlock | null = null; 11 | constructor(ctx: YulFunctionDefinitionContext, visitor: SolidityParserVisitor) { 12 | super(ctx, visitor); 13 | this.name = ctx.YulIdentifier(0)!.getText(); 14 | this.parameters = ctx._arguments.map((arg) => arg.text!); 15 | this.returnParameters = ctx._returnParameters.map((arg) => arg.text!); 16 | this.body = ctx._body?.accept(visitor) ?? null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ast/yul/yul-if-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulIfStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulExpression } from './yul-expression'; 4 | import { YulBlock } from './yul-block'; 5 | 6 | export class YulIfStatement extends BaseNode { 7 | type = 'YulIfStatement' as const; 8 | condition: YulExpression | null = null; 9 | body: YulBlock | null = null; 10 | constructor(ctx: YulIfStatementContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.condition = ctx._cond?.accept(visitor) ?? null; 13 | this.body = ctx._body?.accept(visitor) ?? null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/yul/yul-literal.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { YulLiteralContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class YulLiteral extends BaseNodeString { 5 | type = 'YulLiteral' as const; 6 | constructor(ctx: YulLiteralContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | this.name = 9 | ctx.yulBoolean() !== null ? (ctx.yulBoolean()!.YulTrue() ? 'true' : 'false') : ctx.getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ast/yul/yul-path.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeString } from '../base'; 2 | import { YulPathContext, SolidityParserVisitor } from '../../antlr4'; 3 | 4 | export class YulPath extends BaseNodeString { 5 | type = 'YulPath' as const; 6 | constructor(ctx: YulPathContext, visitor: SolidityParserVisitor) { 7 | super(ctx, visitor); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ast/yul/yul-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulBlock } from './yul-block'; 4 | import { YulVariableDeclaration } from './yul-variable-declaration'; 5 | import { YulAssignment } from './yul-assignment'; 6 | import { YulFunctionCall } from './yul-function-call'; 7 | import { YulIfStatement } from './yul-if-statement'; 8 | import { YulForStatement } from './yul-for-statement'; 9 | import { YulSwitchStatement } from './yul-switch-statement'; 10 | import { YulFunctionDefinition } from './yul-function-definition'; 11 | 12 | type YulExpressionStatement = 13 | | YulBlock 14 | | YulVariableDeclaration 15 | | YulAssignment 16 | | YulFunctionCall 17 | | YulIfStatement 18 | | YulForStatement 19 | | YulSwitchStatement 20 | | YulFunctionDefinition 21 | | 'break' 22 | | 'continue' 23 | | 'leave'; 24 | 25 | export class YulStatement extends BaseNode { 26 | type = 'YulStatement' as const; 27 | expression: YulExpressionStatement | null = null; 28 | constructor(ctx: YulStatementContext, visitor: SolidityParserVisitor) { 29 | super(ctx, visitor); 30 | 31 | const target = [ 32 | ctx.yulBlock(), 33 | ctx.yulVariableDeclaration(), 34 | ctx.yulAssignment(), 35 | ctx.yulFunctionCall(), 36 | ctx.yulIfStatement(), 37 | ctx.yulForStatement(), 38 | ctx.yulSwitchStatement(), 39 | ctx.yulFunctionDefinition(), 40 | ].find(Boolean); 41 | 42 | if (target) { 43 | this.expression = target.accept(visitor); 44 | } else if (ctx.YulBreak() || ctx.YulContinue() || ctx.YulLeave()) { 45 | this.expression = ctx.getText() as any; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ast/yul/yul-switch-case.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulSwitchCaseContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulLiteral } from './yul-literal'; 4 | import { YulBlock } from './yul-block'; 5 | 6 | export class YulSwitchCase extends BaseNode { 7 | type = 'YulSwitchCase' as const; 8 | case: YulLiteral; 9 | body: YulBlock; 10 | constructor(ctx: YulSwitchCaseContext, visitor: SolidityParserVisitor) { 11 | super(ctx, visitor); 12 | this.case = ctx.yulLiteral().accept(visitor); 13 | this.body = ctx.yulBlock().accept(visitor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ast/yul/yul-switch-statement.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulSwitchStatementContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulExpression } from './yul-expression'; 4 | import { YulSwitchCase } from './yul-switch-case'; 5 | import { YulBlock } from './yul-block'; 6 | 7 | export class YulSwitchStatement extends BaseNode { 8 | type = 'YulSwitchStatement' as const; 9 | expression: YulExpression; 10 | switchCases: YulSwitchCase[]; 11 | default: boolean; 12 | body: YulBlock | null = null; 13 | constructor(ctx: YulSwitchStatementContext, visitor: SolidityParserVisitor) { 14 | super(ctx, visitor); 15 | this.expression = ctx.yulExpression().accept(visitor); 16 | this.switchCases = ctx.yulSwitchCase().map((sc) => sc.accept(visitor)); 17 | this.default = !!ctx.YulDefault(); 18 | this.body = ctx.yulBlock()?.accept(visitor) ?? null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ast/yul/yul-variable-declaration.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from '../base'; 2 | import { YulVariableDeclarationContext, SolidityParserVisitor } from '../../antlr4'; 3 | import { YulExpression } from './yul-expression'; 4 | 5 | export class YulVariableDeclaration extends BaseNode { 6 | type = 'YulVariableDeclaration' as const; 7 | identifiers: string[] = []; 8 | expression: YulExpression | null = null; 9 | constructor(ctx: YulVariableDeclarationContext, visitor: SolidityParserVisitor) { 10 | super(ctx, visitor); 11 | this.identifiers = ctx.YulIdentifier().map((i) => i.getText()); 12 | this.expression = ctx.yulExpression()?.accept(visitor) ?? null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/generator/generate.ts: -------------------------------------------------------------------------------- 1 | import type { Options, ParserOptions, Plugin } from 'prettier'; 2 | import * as prettier from 'prettier/standalone'; 3 | import { plugin } from '../prettier'; 4 | import { SyntaxNode } from '../ast'; 5 | import { PrettierParser, getCommentTokens } from '../prettier/parser'; 6 | import { SyntaxToken } from '../parser'; 7 | 8 | export interface GenerateOptions extends Options { 9 | tokens?: SyntaxToken[]; 10 | } 11 | 12 | class PrettierGenerator extends PrettierParser { 13 | public static name = 'solidity-antlr4-generator'; 14 | public parse = (ast: string, _options: ParserOptions) => JSON.parse(ast); 15 | } 16 | 17 | const generatorPlugin: Plugin = { 18 | ...plugin, 19 | parsers: { 20 | [PrettierGenerator.name]: new PrettierGenerator(), 21 | }, 22 | }; 23 | 24 | export const generate = async (ast: SyntaxNode, options: GenerateOptions = {}): Promise => { 25 | if (options.tokens) (ast).comments = getCommentTokens(options.tokens); 26 | return prettier.format(JSON.stringify(ast), { 27 | ...options, 28 | parser: PrettierGenerator.name, 29 | plugins: [...(options.plugins ?? []), generatorPlugin], 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/generator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generate'; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './antlr4'; 2 | export * from './ast'; 3 | export * from './parser'; 4 | export * from './traverse'; 5 | -------------------------------------------------------------------------------- /src/parser/error-listener.ts: -------------------------------------------------------------------------------- 1 | import { Position } from '../ast/base'; 2 | import { 3 | ATNSimulator, 4 | BaseErrorListener, 5 | RecognitionException, 6 | Recognizer, 7 | Token, 8 | } from '../antlr4'; 9 | 10 | export class ParseError extends Error { 11 | constructor( 12 | public message: string, 13 | public position?: Position, 14 | ) { 15 | super(message, { cause: null }); 16 | } 17 | } 18 | 19 | export class SolidityErrorListener extends BaseErrorListener { 20 | errors: ParseError[] = []; 21 | 22 | syntaxError( 23 | _recognizer: Recognizer, 24 | _offendingSymbol: S | null, 25 | line: number, 26 | column: number, 27 | message: string, 28 | _exception: RecognitionException, 29 | ): void { 30 | this.errors.push(new ParseError(message, Position.create(line, column))); 31 | } 32 | 33 | throws = () => { 34 | if (!this.errors.length) return; 35 | throw this.errors[0]; 36 | }; 37 | } 38 | 39 | export const solidityErrorListener = new SolidityErrorListener(); 40 | -------------------------------------------------------------------------------- /src/parser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-listener'; 2 | export * from './parse'; 3 | export * from './tokenizer'; 4 | -------------------------------------------------------------------------------- /src/parser/parse.ts: -------------------------------------------------------------------------------- 1 | import { SolidityASTBuilder } from '../ast/builder'; 2 | import { SourceUnit, SyntaxNode } from '../ast'; 3 | import { SolidityParser, ParseTree, SolidityLexer, CommonTokenStream, CharStreams } from '../antlr4'; 4 | import { ParseError, SolidityErrorListener } from './error-listener'; 5 | 6 | export interface ParseOptions { 7 | tolerant?: boolean; 8 | selector?: (parser: SolidityParser) => ParseTree; 9 | } 10 | 11 | export const defaultParseOption: ParseOptions = { 12 | tolerant: false, 13 | selector: (p) => p.sourceUnit(), 14 | }; 15 | 16 | export const parse = ( 17 | source: string, 18 | _options?: ParseOptions, 19 | ): T => { 20 | let syntaxTree: T; 21 | const options: ParseOptions = Object.assign({}, defaultParseOption, _options); 22 | const listener = new SolidityErrorListener(); 23 | 24 | try { 25 | const input = CharStreams.fromString(source); 26 | const lexer = new SolidityLexer(input); 27 | const token = new CommonTokenStream(lexer); 28 | const parser = new SolidityParser(token); 29 | parser.removeErrorListeners(); 30 | parser.addErrorListener(listener); 31 | 32 | const visitor = new SolidityASTBuilder(); 33 | const parseTree = options.selector!(parser); 34 | syntaxTree = parseTree.accept(visitor)! as T; 35 | } catch (error) { 36 | if (error instanceof ParseError) { 37 | } else { 38 | listener.errors.push(new ParseError((error as any).message || 'unknown error')); 39 | } 40 | } 41 | 42 | if (!options.tolerant) listener.throws(); 43 | return syntaxTree!; 44 | }; 45 | -------------------------------------------------------------------------------- /src/parser/tokenizer.ts: -------------------------------------------------------------------------------- 1 | import { Position } from '../ast/base'; 2 | import { SolidityLexer, CharStreams } from '../antlr4'; 3 | import { ParseError, SolidityErrorListener } from './error-listener'; 4 | 5 | export type SyntaxTokenType = (typeof SolidityLexer.symbolicNames)[number]; 6 | 7 | export interface SyntaxToken { 8 | text: string; 9 | type: SyntaxTokenType; 10 | range: [number, number]; 11 | position: Position; 12 | } 13 | 14 | export interface TokenizerOptions { 15 | tolerant?: boolean; 16 | } 17 | 18 | export const defaultTokenizerOption: TokenizerOptions = { 19 | tolerant: false, 20 | }; 21 | 22 | export const tokenizer = (source: string, _options: TokenizerOptions = {}): SyntaxToken[] => { 23 | let tokens: SyntaxToken[] = []; 24 | const options: TokenizerOptions = Object.assign({}, defaultTokenizerOption, _options); 25 | const listener = new SolidityErrorListener(); 26 | 27 | try { 28 | const input = CharStreams.fromString(source); 29 | const lexer = new SolidityLexer(input); 30 | lexer.removeErrorListeners(); 31 | lexer.addErrorListener(listener); 32 | 33 | tokens = lexer.getAllTokens().map((token) => { 34 | return { 35 | text: token.text!, 36 | type: SolidityLexer.symbolicNames[token.type]!, 37 | range: [token.start, token.stop], 38 | position: Position.create(token.line, token.column), 39 | }; 40 | }); 41 | } catch (error) { 42 | if (error instanceof ParseError) { 43 | } else { 44 | listener.errors.push(new ParseError((error as any).message || 'unknown error')); 45 | } 46 | } 47 | 48 | if (!options.tolerant) listener.throws(); 49 | return tokens; 50 | }; 51 | -------------------------------------------------------------------------------- /src/prettier/index.ts: -------------------------------------------------------------------------------- 1 | import type { SupportLanguage, Parser, Printer, Plugin } from 'prettier'; 2 | import { PrettierParser } from './parser'; 3 | import { SyntaxNode } from '../ast'; 4 | import { PrettierPrinter } from './printer'; 5 | import { defaultOptions, options } from './options'; 6 | 7 | export const languages: SupportLanguage[] = [ 8 | { 9 | name: 'Solidity', 10 | tmScope: 'source.solidity', 11 | extensions: ['.sol'], 12 | parsers: [PrettierParser.name], 13 | vscodeLanguageIds: ['solidity'], 14 | }, 15 | ]; 16 | 17 | export const parsers: Record> = { 18 | [PrettierParser.name]: new PrettierParser(), 19 | }; 20 | 21 | export const printers: Record> = { 22 | [PrettierPrinter.name]: new PrettierPrinter(), 23 | }; 24 | 25 | export const plugin: Plugin = { 26 | languages, 27 | parsers, 28 | printers, 29 | options, 30 | defaultOptions, 31 | }; 32 | 33 | export const parserName = PrettierParser.name; 34 | 35 | export default plugin; 36 | -------------------------------------------------------------------------------- /src/prettier/options.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'prettier'; 2 | 3 | const category = 'Solidity'; 4 | 5 | export const options: Plugin['options'] = { 6 | printWidth: { 7 | category, 8 | type: 'int', 9 | default: 80, 10 | description: 'The line length where Prettier will try wrap.', 11 | range: { start: 0, end: Number.POSITIVE_INFINITY, step: 1 }, 12 | }, 13 | tabWidth: { 14 | category, 15 | type: 'int', 16 | default: 2, 17 | description: 'Number of spaces per indentation level.', 18 | range: { start: 0, end: Number.POSITIVE_INFINITY, step: 1 }, 19 | }, 20 | useTabs: { 21 | category, 22 | type: 'boolean', 23 | default: false, 24 | description: 'Indent with tabs instead of spaces.', 25 | }, 26 | singleQuote: { 27 | category, 28 | type: 'boolean', 29 | default: false, 30 | description: 'Use single quotes instead of double quotes.', 31 | }, 32 | bracketSpacing: { 33 | category: 'common', 34 | type: 'boolean', 35 | default: true, 36 | description: 'Print spaces between brackets.', 37 | oppositeDescription: 'Do not print spaces between brackets.', 38 | }, 39 | endOfLine: { 40 | category, 41 | type: 'choice', 42 | default: 'lf', 43 | description: 'Which end of line characters to apply.', 44 | choices: [ 45 | { 46 | value: 'lf', 47 | description: 'Line Feed only (\\n), common on Linux and macOS as well as inside git repos', 48 | }, 49 | { 50 | value: 'crlf', 51 | description: 'Carriage Return + Line Feed characters (\\r\\n), common on Windows', 52 | }, 53 | { 54 | value: 'cr', 55 | description: 'Carriage Return character only (\\r), used very rarely', 56 | }, 57 | { 58 | value: 'auto', 59 | description: `Maintain existing (mixed values within one file are normalised by looking at what's used after the first line)`, 60 | }, 61 | ], 62 | }, 63 | }; 64 | 65 | export const defaultOptions: Plugin['defaultOptions'] = Object.fromEntries( 66 | Object.entries(options).map(([k, v]) => [k, v.default]), 67 | ); 68 | -------------------------------------------------------------------------------- /src/prettier/parser.ts: -------------------------------------------------------------------------------- 1 | import type { Parser, ParserOptions } from 'prettier'; 2 | import { SyntaxNode } from '../ast'; 3 | import { PrettierPrinter } from './printer'; 4 | import { SyntaxToken, parse, tokenizer } from '../parser'; 5 | import { WithComments, comments } from './printers/base'; 6 | 7 | export const getCommentTokens = (tokens: SyntaxToken[]) => { 8 | return tokens 9 | .filter((token) => comments.includes(token.type)) 10 | .map((c) => ({ ...c, value: c.text })); 11 | }; 12 | 13 | export class PrettierParser implements Parser { 14 | public static name = 'solidity-antlr4'; 15 | public astFormat = PrettierPrinter.name; 16 | public locStart = (node: SyntaxNode) => node.range[0]; 17 | public locEnd = (node: SyntaxNode) => node.range[1] + 1; // TODO@jeason: Why +1? 18 | public parse = (text: string, _options: ParserOptions) => { 19 | const ast = parse(text, { tolerant: true }) as WithComments; 20 | const tokens = tokenizer(text, { tolerant: true }); 21 | if (ast) (ast).comments = getCommentTokens(tokens); 22 | return ast; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/prettier/printer.ts: -------------------------------------------------------------------------------- 1 | import * as ast from '../ast'; 2 | import type { AstPath, Doc, ParserOptions, Printer } from 'prettier'; 3 | import { 4 | printComment, 5 | isBlockComment, 6 | canAttachComment, 7 | print, 8 | massageAstNode, 9 | handleComments, 10 | } from './printers'; 11 | 12 | export type PrintFunc = (arg: { 13 | path: AstPath; 14 | options: ParserOptions; 15 | print: (path: AstPath) => Doc; 16 | args?: any; 17 | }) => Doc; 18 | 19 | export class PrettierPrinter implements Printer { 20 | public static name = 'solidity-antlr4-ast'; 21 | 22 | public print = print; 23 | public printComment = printComment; 24 | public isBlockComment = isBlockComment; 25 | public canAttachComment = canAttachComment; 26 | public massageAstNode = massageAstNode; 27 | public handleComments = handleComments; 28 | 29 | // handleComments = { 30 | // ownLine: this.ownLine, 31 | // endOfLine: this.endOfLine, 32 | // remaining: this.remaining, 33 | // }; 34 | } 35 | -------------------------------------------------------------------------------- /src/prettier/printers/base.ts: -------------------------------------------------------------------------------- 1 | import * as ast from '../../ast'; 2 | import type { AstPath, Doc, ParserOptions } from 'prettier'; 3 | import * as doc from 'prettier/doc'; 4 | import * as prettier from 'prettier/standalone'; 5 | import { SyntaxTokenType, SyntaxToken } from '../../parser'; 6 | import { printComment } from './comment'; 7 | 8 | export { Doc }; 9 | 10 | export type CommentToken = SyntaxToken & { 11 | placement: 'remaining' | 'ownLine' | 'endOfLine'; 12 | leading: boolean; 13 | trailing: boolean; 14 | printed: boolean; 15 | nodeDescription: string; 16 | }; 17 | 18 | export type WithComments = T & { 19 | comments?: CommentToken[]; 20 | }; 21 | 22 | export type PrintFunc = (arg: { 23 | node: WithComments; 24 | path: AstPath>; 25 | options: ParserOptions>; 26 | print: (path: AstPath) => Doc; 27 | args?: any; 28 | }) => Doc; 29 | 30 | export const lineComments: SyntaxTokenType[] = [ 31 | 'LINE_COMMENT', 32 | 'AssemblyBlockLINE_COMMENT', 33 | 'YulLINE_COMMENT', 34 | 'PragmaLINE_COMMENT', 35 | ]; 36 | export const blockComments: SyntaxTokenType[] = [ 37 | 'COMMENT', 38 | 'AssemblyBlockCOMMENT', 39 | 'YulCOMMENT', 40 | 'PragmaCOMMENT', 41 | ]; 42 | export const comments: SyntaxTokenType[] = [...lineComments, ...blockComments]; 43 | 44 | export class BasePrinter { 45 | readonly space = ' '; 46 | readonly dot = '.'; 47 | readonly comma = ','; 48 | readonly semi = ';'; 49 | readonly quote = `"`; 50 | readonly singleQuote = `'`; 51 | readonly builders = doc.builders; 52 | 53 | constructor( 54 | public readonly options: ParserOptions, 55 | public readonly print: (path: AstPath) => Doc, 56 | ) {} 57 | 58 | // The pangu space 59 | pangu = (path: AstPath) => { 60 | return (prettier as any).util.isNextLineEmpty( 61 | this.options.originalText, 62 | this.options.locEnd(path.node), 63 | ) 64 | ? this.builders.hardline 65 | : ''; 66 | }; 67 | // value => "value" 68 | literal = (value: Doc) => { 69 | return this.options.singleQuote 70 | ? [this.singleQuote, value, this.singleQuote] 71 | : [this.quote, value, this.quote]; 72 | }; 73 | // value[] => value1, value2, value3 74 | paramater = (value: Doc[], sep: Doc = [this.comma, this.builders.line]) => { 75 | return this.builders.join(sep, value); 76 | }; 77 | // value = { value } 78 | block = ( 79 | value: Doc, 80 | options: { 81 | empty?: boolean; 82 | groupId?: symbol; 83 | shouldBreak?: boolean; 84 | unGroup?: boolean; 85 | } = {}, 86 | ) => { 87 | const { 88 | empty = false, 89 | groupId = Symbol('block'), 90 | shouldBreak = false, 91 | unGroup = false, 92 | } = options; 93 | if (empty) { 94 | return unGroup ? '{}' : this.builders.group(['{', '}'], { id: groupId, shouldBreak }); 95 | } 96 | const line = this.options.bracketSpacing ? this.builders.line : this.builders.softline; 97 | if (unGroup) return ['{', line, value, line, '}']; 98 | const beforeLine = this.builders.indentIfBreak(line, { groupId }); 99 | const content = this.builders.indentIfBreak(value, { groupId }); 100 | return this.builders.group(['{', beforeLine, content, line, '}'], { id: groupId, shouldBreak }); 101 | }; 102 | // value => (value) 103 | tuple = ( 104 | value: Doc, 105 | options: { 106 | groupId?: symbol; 107 | shouldBreak?: boolean; 108 | unGroup?: boolean; 109 | } = {}, 110 | ) => { 111 | const { groupId = Symbol('tuple'), shouldBreak = false, unGroup = false } = options; 112 | if (unGroup) return ['(', value, ')']; 113 | const content = this.builders.indentIfBreak(value, { groupId }); 114 | const line = this.builders.softline; 115 | return this.builders.group( 116 | ['(', this.builders.indentIfBreak(line, { groupId }), content, line, ')'], 117 | { id: groupId, shouldBreak }, 118 | ); 119 | }; 120 | // value => [value] 121 | list = ( 122 | value: Doc, 123 | options: { 124 | groupId?: symbol; 125 | shouldBreak?: boolean; 126 | unGroup?: boolean; 127 | } = {}, 128 | ) => { 129 | const { groupId = Symbol('list'), shouldBreak = false, unGroup = false } = options; 130 | if (unGroup) return ['[', value, ']']; 131 | const content = this.builders.indentIfBreak(value, { groupId }); 132 | const line = this.builders.softline; 133 | return this.builders.group( 134 | ['[', this.builders.indentIfBreak(line, { groupId }), content, line, ']'], 135 | { id: groupId, shouldBreak }, 136 | ); 137 | }; 138 | // patch unprinted comments 139 | comments = (p: AstPath) => { 140 | if (!p.node?.comments?.length) return ''; 141 | const parts: Doc[] = []; 142 | p.map((commentPath) => { 143 | const comment = commentPath.node; 144 | if (!comment.trailing && !comment.leading && !comment.printed) { 145 | comment.printed = true; 146 | parts.push(printComment!(commentPath, this.options as any)); 147 | } 148 | }, 'comments'); 149 | return this.builders.join(this.builders.line, parts); 150 | }; 151 | } 152 | -------------------------------------------------------------------------------- /src/prettier/printers/comment.ts: -------------------------------------------------------------------------------- 1 | import * as doc from 'prettier/doc'; 2 | import type { Printer } from 'prettier'; 3 | import { blockComments, comments, CommentToken } from './base'; 4 | 5 | export const isBlockComment: Printer['isBlockComment'] = (node: any) => { 6 | return node?.type && blockComments.includes(node.type); 7 | }; 8 | 9 | export const canAttachComment: Printer['canAttachComment'] = (node: any) => 10 | !!node?.type && !comments.includes(node.type); 11 | 12 | // TODO: needs a comment parser 13 | export const printComment: Printer['printComment'] = (path, _options) => { 14 | const comment = path.node; 15 | console.log(JSON.stringify(comment)); 16 | if (!comment.text) return ''; 17 | 18 | if (comment.text.startsWith('///')) { 19 | // means natspec format comment: 20 | // https://docs.soliditylang.org/en/v0.8.24/natspec-format.html#natspec 21 | const content = comment.text.replace('///', '').trim(); 22 | return ['/// ', content]; 23 | } 24 | 25 | if (comment.text.startsWith('//')) { 26 | const content = comment.text.replace('//', '').trim(); 27 | return ['// ', content]; 28 | } 29 | 30 | const isMultiline = comment.text.includes('\n'); 31 | const multiline = isMultiline ? doc.builders.hardline : doc.builders.line; 32 | 33 | if (comment.text.startsWith('/**') && comment.text.endsWith('*/')) { 34 | const lines = comment.text 35 | .slice(3, -2) 36 | .split('\n') 37 | .map((line) => { 38 | const trimmed = line.trim(); 39 | if (trimmed.startsWith('*')) { 40 | return trimmed.slice(1).trim(); 41 | } 42 | return trimmed; 43 | }) 44 | .filter(Boolean); 45 | 46 | return [ 47 | '/**', 48 | multiline, 49 | doc.builders.join( 50 | multiline, 51 | lines.map((line) => [' * ', line]), 52 | ), 53 | multiline, 54 | ' */', 55 | ]; 56 | } 57 | 58 | if (comment.text.startsWith('/*') && comment.text.endsWith('*/')) { 59 | const lines = comment.text 60 | .slice(2, -2) 61 | .split('\n') 62 | .map((line) => line.trim()) 63 | .filter(Boolean); 64 | return ['/*', multiline, doc.builders.join(multiline, lines), multiline, '*/']; 65 | } 66 | 67 | return comment.text; 68 | }; 69 | 70 | // Prettier offers a clean way to define ignored properties. 71 | export const massageAstNode: Printer['massageAstNode'] = () => {}; 72 | (massageAstNode).ignoredProperties = new Set(['location', 'range', 'comments']); 73 | 74 | export const handleComments: Printer['handleComments'] = { 75 | ownLine: (commentNode, text, options, ast, isLastComment) => false, 76 | endOfLine: (commentNode, text, options, ast, isLastComment) => false, 77 | remaining: (commentNode, text, options, ast, isLastComment) => false, 78 | }; 79 | -------------------------------------------------------------------------------- /src/prettier/printers/expression.ts: -------------------------------------------------------------------------------- 1 | import * as ast from '../../ast'; 2 | import { BasePrinter, Doc, PrintFunc } from './base'; 3 | 4 | export class PrinterExpression 5 | extends BasePrinter 6 | implements Record<`print${ast.ExpressionNodeType}`, PrintFunc> 7 | { 8 | printAssignOp: PrintFunc = ({ node }) => node.name; 9 | printBinaryOperation: PrintFunc = ({ node, path, print }) => { 10 | const parts: Doc[] = []; 11 | if (node.left !== null) parts.push(path.call(print, 'left')); 12 | if (node.operator !== null) parts.push(this.space, node.operator); 13 | if (node.right !== null) parts.push(this.builders.line, path.call(print, 'right')); 14 | return this.builders.group(parts); 15 | }; 16 | printAssignment: PrintFunc = ({ node, path, print }) => { 17 | const parts: Doc[] = []; 18 | if (node.left !== null) parts.push(path.call(print, 'left')); 19 | if (node.operator !== null) parts.push(this.space, node.operator); 20 | if (node.right !== null) parts.push(this.builders.line, path.call(print, 'right')); 21 | return this.builders.group(parts); 22 | }; 23 | printBooleanLiteral: PrintFunc = ({ node }) => 24 | node.value === true ? 'true' : 'false'; 25 | printCallArgumentList: PrintFunc = ({ path, print, node }) => { 26 | if (node.namedArguments.length) { 27 | return this.tuple( 28 | this.block(this.paramater(path.map(print, 'namedArguments')), { 29 | empty: !node.namedArguments.length, 30 | }), 31 | { unGroup: true }, 32 | ); 33 | } else if (node.expressions.length) { 34 | return this.tuple(this.paramater(path.map(print, 'expressions'))); 35 | } 36 | return this.tuple(''); 37 | }; 38 | printConditional: PrintFunc = ({ path, print }) => { 39 | const groupId = Symbol('conditional'); 40 | const breakLine = this.builders.indentIfBreak(this.builders.line, { groupId }); 41 | return this.builders.group( 42 | [ 43 | path.call(print, 'condition'), 44 | breakLine, 45 | '?', 46 | breakLine, 47 | path.call(print, 'trueExpression'), 48 | breakLine, 49 | ':', 50 | breakLine, 51 | path.call(print, 'falseExpression'), 52 | ], 53 | { id: groupId }, 54 | ); 55 | }; 56 | printFunctionCallOptions: PrintFunc = ({ path, print }) => { 57 | return [ 58 | path.call(print, 'expression'), 59 | this.block(this.paramater(path.map(print, 'namedArguments'))), 60 | ]; 61 | }; 62 | printFunctionCall: PrintFunc = ({ path, print }) => { 63 | return [path.call(print, 'expression'), path.call(print, 'arguments')]; 64 | }; 65 | printHexStringLiteral: PrintFunc = ({ node }) => [ 66 | 'hex', 67 | this.literal(node.value), 68 | ]; 69 | printIdentifier: PrintFunc = ({ node }) => node.name; 70 | printIndexAccess: PrintFunc = ({ node, path, print }) => { 71 | return [ 72 | path.call(print, 'baseExpression'), 73 | this.list(node.indexExpression ? path.call(print, 'indexExpression') : ''), 74 | ]; 75 | }; 76 | printIndexRangeAccess: PrintFunc = ({ path, print }) => { 77 | const inner = [path.call(print, 'startExpression'), ':', path.call(print, 'endExpression')]; 78 | return [path.call(print, 'baseExpression'), this.list(inner)]; 79 | }; 80 | printInlineArray: PrintFunc = ({ path, print }) => { 81 | return this.list(this.paramater(path.map(print, 'expressions'))); 82 | }; 83 | printMemberAccess: PrintFunc = ({ node, path, print }) => { 84 | return [path.call(print, 'expression'), this.dot, node.memberName!]; 85 | }; 86 | printMetaType: PrintFunc = ({ path, print }) => { 87 | return ['type', '(', path.call(print, 'typeName'), ')']; 88 | }; 89 | printNamedArgument: PrintFunc = ({ path, print }) => { 90 | return [path.call(print, 'name'), ':', this.space, path.call(print, 'expression')]; 91 | }; 92 | printNewExpr: PrintFunc = ({ path, print }) => { 93 | return ['new', this.space, path.call(print, 'typeName')]; 94 | }; 95 | printNumberLiteral: PrintFunc = ({ node }) => { 96 | if (node.hexValue !== null) return node.hexValue; 97 | const parts = [node.value!]; 98 | if (node.subDenomination !== null) parts.push(this.space, node.subDenomination); 99 | return parts; 100 | }; 101 | printPayableConversion: PrintFunc = ({ path, print }) => { 102 | return ['payable', path.call(print, 'arguments')]; 103 | }; 104 | printStringLiteral: PrintFunc = ({ node }) => this.literal(node.value); 105 | printTupleExpression: PrintFunc = ({ path, print }) => { 106 | return this.tuple(this.paramater(path.map(print, 'expressions'))); 107 | }; 108 | printUnaryOperation: PrintFunc = ({ node, path, print }) => { 109 | const parts: Doc[] = []; 110 | if (node.left !== null) parts.push(path.call(print, 'left')); 111 | if (node.operator !== null) parts.push(node.operator); 112 | if (node.operator === 'delete') parts.push(this.space); // shit this 113 | if (node.right !== null) parts.push(path.call(print, 'right')); 114 | return this.space, parts; 115 | }; 116 | printUnicodeStringLiteral: PrintFunc = ({ node }) => [ 117 | 'unicode', 118 | this.literal(node.value), 119 | ]; 120 | printUserDefinableOperator: PrintFunc = ({ node }) => node.name; 121 | } 122 | -------------------------------------------------------------------------------- /src/prettier/printers/index.ts: -------------------------------------------------------------------------------- 1 | import type { Printer } from 'prettier'; 2 | import * as ast from '../../ast'; 3 | import { PrintFunc } from './base'; 4 | 5 | import { PrinterDeclaration } from './declaration'; 6 | import { PrinterExpression } from './expression'; 7 | import { PrinterMeta } from './meta'; 8 | import { PrinterStatement } from './statement'; 9 | import { PrinterType } from './type'; 10 | import { PrinterYul } from './yul'; 11 | 12 | export * from './comment'; 13 | 14 | export const print: Printer['print'] = (path, options, _print) => { 15 | const node = path.node; 16 | // check exist 17 | if (node === null) return ''; 18 | 19 | // check array 20 | if (Array.isArray(node)) return path.map(_print); 21 | 22 | const printerName = `print${node.type}` as `print${ast.SyntaxNodeType}`; 23 | const mixin: Record<`print${ast.SyntaxNodeType}`, PrintFunc> = Object.assign( 24 | {}, 25 | new PrinterDeclaration(options, _print), 26 | new PrinterExpression(options, _print), 27 | new PrinterMeta(options, _print), 28 | new PrinterStatement(options, _print), 29 | new PrinterType(options, _print), 30 | new PrinterYul(options, _print), 31 | ); 32 | const printer = mixin[printerName]; 33 | if (!printer) throw new Error(`missing printer for node type "${node.type}"`); 34 | 35 | // print 36 | const document = printer({ path, options, print: _print, node }); 37 | 38 | // debug 39 | // if (node.comments?.some((c) => !c.printed)) { 40 | // console.log(node.type, node.comments?.length, node.comments); 41 | // } 42 | 43 | return document; 44 | }; 45 | -------------------------------------------------------------------------------- /src/prettier/printers/meta.ts: -------------------------------------------------------------------------------- 1 | import * as ast from '../../ast'; 2 | import { BasePrinter, Doc, PrintFunc } from './base'; 3 | 4 | export class PrinterMeta 5 | extends BasePrinter 6 | implements Record<`print${ast.MetaNodeType}`, PrintFunc> 7 | { 8 | printDataLocation: PrintFunc = ({ node }) => node.name; 9 | printIdentifierPath: PrintFunc = ({ path, print }) => 10 | this.builders.join(this.dot, path.map(print, 'identifiers')); 11 | printImportAliases: PrintFunc = ({ node, path, print }) => { 12 | const parts = [path.call(print, 'foreign')]; 13 | if (node.local) parts.push(this.space, 'as', this.space, path.call(print, 'local')); 14 | return parts; 15 | }; 16 | printImportDirective: PrintFunc = ({ node, path, print }) => { 17 | let result: Doc = ''; 18 | const importPath = path.call(print, 'path'); 19 | if (node.unitAlias) { 20 | const unitAlias = path.call(print, 'unitAlias'); 21 | if (node.importAll) { 22 | result = this.builders.join(this.space, [ 23 | 'import', 24 | '*', 25 | 'as', 26 | unitAlias, 27 | 'from', 28 | importPath, 29 | ]); 30 | } else { 31 | result = this.builders.join(this.space, ['import', importPath, 'as', unitAlias]); 32 | } 33 | } else if (node.symbolAliases.length) { 34 | result = this.builders.join(this.space, [ 35 | 'import', 36 | this.block(this.paramater(path.map(print, 'symbolAliases'))), 37 | 'from', 38 | importPath, 39 | ]); 40 | } else { 41 | result = this.builders.join(this.space, ['import', importPath]); 42 | } 43 | return [result, this.semi]; 44 | }; 45 | printInheritanceSpecifier: PrintFunc = ({ node, path, print }) => { 46 | const parts: Doc[] = [path.call(print, 'baseName')]; 47 | if (node.arguments !== null) { 48 | parts.push(path.call(print, 'arguments')); 49 | } 50 | return parts; 51 | }; 52 | printModifierInvocation: PrintFunc = ({ node, path, print }) => { 53 | const name = path.call(print, 'name'); 54 | if (node.arguments === null) return name; 55 | return this.builders.group([name, path.call(print, 'arguments')]); 56 | }; 57 | printPath: PrintFunc = ({ node }) => this.literal(node.name); 58 | printPragmaDirective: PrintFunc = ({ node }) => { 59 | const groupId = Symbol('pragma'); 60 | const line = this.builders.indentIfBreak(this.builders.line, { groupId }); 61 | const literal = this.builders.join(line, node.literals); 62 | return this.builders.group(['pragma', line, literal, this.semi], { id: groupId }); 63 | }; 64 | printSourceUnit: PrintFunc = ({ path, print }) => { 65 | const contents: Doc[] = []; 66 | path.map((nodePath) => { 67 | contents.push(nodePath.call(print)); 68 | contents.push(this.builders.hardline); 69 | if ( 70 | !!nodePath.next && 71 | (nodePath.node?.type !== nodePath.next?.type || 72 | nodePath.next?.type === 'ContractDefinition') 73 | ) { 74 | contents.push(this.builders.hardline); 75 | } 76 | }, 'nodes'); 77 | return contents; 78 | }; 79 | printUsingAliases: PrintFunc = ({ node, path, print }) => { 80 | const parts: Doc[] = [path.call(print, 'name')]; 81 | if (node.operator !== null) 82 | parts.push(this.space, 'as', this.space, path.call(print, 'operator')); 83 | return parts; 84 | }; 85 | printUsingDirective: PrintFunc = ({ node, path, print }) => { 86 | const content: Doc[] = [this.space]; 87 | if (node.usingAliases !== null && node.usingAliases.length) { 88 | content.push(this.block(this.paramater(path.map(print, 'usingAliases')))); 89 | } else { 90 | content.push(path.call(print, 'libraryName')); 91 | } 92 | const typeName: Doc = node.typeName === '*' ? '*' : path.call(print as any, 'typeName'); 93 | const globalFlag: Doc = node.global ? [this.space, 'global'] : ''; 94 | return ['using', content, this.space, 'for', this.space, typeName, globalFlag, this.semi]; 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/prettier/printers/statement.ts: -------------------------------------------------------------------------------- 1 | import * as ast from '../../ast'; 2 | import { BasePrinter, Doc, PrintFunc } from './base'; 3 | 4 | export class PrinterStatement 5 | extends BasePrinter 6 | implements Record<`print${ast.StatementNodeType}`, PrintFunc> 7 | { 8 | printAssemblyStatement: PrintFunc = ({ node, path, print }) => { 9 | const parts: Doc[] = ['assembly']; 10 | if (node.dialect !== null) { 11 | parts.push(this.space, this.quote, node.dialect, this.quote); 12 | } 13 | if (node.flags !== null) { 14 | parts.push( 15 | this.space, 16 | this.tuple(this.paramater((node.flags ?? []).map((f) => [this.quote, f, this.quote]))), 17 | ); 18 | } 19 | 20 | const statements = path.map((p) => [print(p), this.pangu(p)], 'yulStatements'); 21 | parts.push( 22 | this.space, 23 | this.block(this.builders.join(this.builders.hardline, statements), { 24 | empty: !node.yulStatements.length, 25 | }), 26 | ); 27 | return this.builders.group(parts); 28 | }; 29 | printBlock: PrintFunc = ({ node, path, print }) => { 30 | const statements: Doc[] = []; 31 | path.map((p) => statements.push([print(p), this.pangu(p)]), 'statements'); 32 | if (node.comments?.length) { 33 | statements.push(...this.comments(path)); 34 | } 35 | const parts = this.block(this.builders.join(this.builders.line, statements), { 36 | empty: !statements.length, 37 | shouldBreak: true, 38 | }); 39 | if (node.unchecked) { 40 | return ['unchecked', this.space, parts]; 41 | } 42 | return parts; 43 | }; 44 | printBreakStatement: PrintFunc = ({ node }) => [node.name, this.semi]; 45 | printCatchClause: PrintFunc = ({ node, path, print }) => { 46 | const groupId = Symbol('catch'); 47 | const parts: Doc[] = ['catch']; 48 | if (node.errorName !== null || node.parameters !== null) parts.push(this.space); 49 | if (node.errorName !== null) parts.push(path.call(print, 'errorName')); 50 | if (node.parameters !== null) { 51 | parts.push(this.tuple(this.paramater(path.map(print, 'parameters')), { groupId })); 52 | } 53 | return [this.builders.group(parts, { id: groupId }), this.space, path.call(print, 'body')]; 54 | }; 55 | printContinueStatement: PrintFunc = ({ node }) => [node.name, this.semi]; 56 | printDoWhileStatement: PrintFunc = ({ path, print }) => { 57 | const parts: Doc[] = ['while', this.space, this.tuple(path.call(print, 'condition'))]; 58 | return ['do', this.space, path.call(print, 'body'), this.space, this.builders.group(parts)]; 59 | }; 60 | printEmitStatement: PrintFunc = ({ path, print }) => { 61 | return this.builders.group([ 62 | 'emit', 63 | this.space, 64 | path.call(print, 'expression'), 65 | path.call(print, 'arguments'), 66 | this.semi, 67 | ]); 68 | }; 69 | printExpressionStatement: PrintFunc = ({ path, print }) => { 70 | return [path.call(print, 'expression'), this.semi]; 71 | }; 72 | printForStatement: PrintFunc = ({ node, path, print }) => { 73 | const parts: Doc[] = ['for']; 74 | const initialization = 75 | node.initializationExpression === null ? ';' : path.call(print, 'initializationExpression'); 76 | const condition = 77 | node.conditionExpression === null ? ';' : path.call(print, 'conditionExpression'); 78 | const loop = node.loopExpression === null ? '' : path.call(print, 'loopExpression'); 79 | parts.push( 80 | this.space, 81 | this.tuple(this.builders.join(this.space, [initialization, condition, loop])), 82 | ); 83 | parts.push(this.space, path.call(print, 'body')); 84 | return this.builders.group(parts); 85 | }; 86 | printIfStatement: PrintFunc = ({ node, path, print }) => { 87 | const parts: Doc[] = [ 88 | 'if', 89 | this.space, 90 | this.tuple(path.call(print, 'condition')), 91 | this.space, 92 | path.call(print, 'ifStatement'), 93 | ]; 94 | if (node.elseStatement !== null) { 95 | parts.push(this.space, 'else', this.space, path.call(print, 'elseStatement')); 96 | } 97 | return this.builders.group(parts); 98 | }; 99 | printReturnStatement: PrintFunc = ({ node, path, print }) => { 100 | const parts: Doc[] = ['return']; 101 | if (node.expression !== null) { 102 | parts.push(this.space, path.call(print, 'expression')); 103 | } 104 | parts.push(this.semi); 105 | return this.builders.group(parts); 106 | }; 107 | printRevertStatement: PrintFunc = ({ path, print }) => { 108 | return this.builders.group([ 109 | 'revert', 110 | this.space, 111 | path.call(print, 'expression'), 112 | path.call(print, 'arguments'), 113 | this.semi, 114 | ]); 115 | }; 116 | printTryStatement: PrintFunc = ({ node, print, path }) => { 117 | const parts: Doc[] = ['try', this.space, path.call(print, 'expression')]; 118 | if (node.returnParameters !== null) { 119 | parts.push(this.space, [ 120 | 'returns', 121 | this.space, 122 | this.tuple(this.paramater(path.map(print, 'returnParameters'))), 123 | ]); 124 | } 125 | parts.push(this.space, path.call(print, 'body')); 126 | parts.push(this.space, this.builders.join(this.space, path.map(print, 'catchClauses'))); 127 | return this.builders.group(parts); 128 | }; 129 | printVariableDeclarationStatement: PrintFunc = ({ 130 | node, 131 | path, 132 | print, 133 | }) => { 134 | const parts: Doc[] = []; 135 | if (node.variableTuple !== null) { 136 | parts.push(this.tuple(this.paramater(path.map(print, 'variableTuple')))); 137 | } else { 138 | parts.push(path.call(print, 'variable')); 139 | } 140 | if (node.expression !== null) { 141 | parts.push(this.space, '=', this.space, path.call(print, 'expression')); 142 | } 143 | parts.push(this.semi); 144 | return this.builders.group(parts); 145 | }; 146 | printWhileStatement: PrintFunc = ({ path, print }) => { 147 | const parts: Doc[] = ['while', this.space, this.tuple(path.call(print, 'condition'))]; 148 | return [this.builders.group(parts), this.space, path.call(print, 'body')]; 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /src/prettier/printers/type.ts: -------------------------------------------------------------------------------- 1 | import * as ast from '../../ast'; 2 | import { BasePrinter, Doc, PrintFunc } from './base'; 3 | 4 | export class PrinterType 5 | extends BasePrinter 6 | implements Record<`print${ast.TypeNodeType}`, PrintFunc> 7 | { 8 | printElementaryTypeName: PrintFunc = ({ node }) => { 9 | const parts: Doc[] = [node.name]; 10 | if (node.name === 'address' && node.payable) parts.push(this.space, 'payable'); 11 | return parts; 12 | }; 13 | printFunctionTypeName: PrintFunc; // TODO: implement in declaration.ts 14 | printMappingKeyType: PrintFunc = ({ node }) => node.name; 15 | printMappingType: PrintFunc = ({ path, print }) => { 16 | return [ 17 | 'mapping', 18 | this.tuple([ 19 | path.call(print, 'keyType'), 20 | this.space, 21 | '=>', 22 | this.space, 23 | path.call(print, 'valueType'), 24 | ]), 25 | ]; 26 | }; 27 | printTypeName: PrintFunc = (args) => { 28 | const { node, path, print } = args; 29 | if (node.type === 'TypeName') { 30 | return [path.call(print, 'baseType'), this.list(path.call(print, 'expression'))]; 31 | } 32 | throw new Error('Unknown node type: ' + node.type); // unreachable 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/prettier/printers/yul.ts: -------------------------------------------------------------------------------- 1 | import * as ast from '../../ast'; 2 | import { BasePrinter, PrintFunc, Doc } from './base'; 3 | 4 | export class PrinterYul 5 | extends BasePrinter 6 | implements Record<`print${ast.YulNodeType}`, PrintFunc> 7 | { 8 | printYulAssignment: PrintFunc = ({ path, print }) => { 9 | return [ 10 | this.paramater(path.map(print, 'paths')), 11 | this.space, 12 | ':=', 13 | this.space, 14 | path.call(print, 'expression'), 15 | ]; 16 | }; 17 | printYulBlock: PrintFunc = ({ node, path, print }) => { 18 | const statements = path.map((p) => [print(p), this.pangu(p)], 'statements'); 19 | return this.block(this.builders.join(this.builders.line, statements), { 20 | empty: !node.statements.length, 21 | }); 22 | }; 23 | printYulBoolean: PrintFunc = ({ node }) => node.name; 24 | printYulForStatement: PrintFunc = ({ path, print }) => { 25 | const forConditions: Doc[] = [ 26 | this.builders.line, 27 | path.call(print, 'initializationBlock'), 28 | this.builders.line, 29 | path.call(print, 'conditionExpression'), 30 | this.builders.line, 31 | path.call(print, 'loopBlock'), 32 | ]; 33 | return this.builders.group([ 34 | 'for', 35 | this.builders.ifBreak(this.builders.indent(forConditions), forConditions), 36 | this.builders.line, 37 | path.call(print, 'body'), 38 | ]); 39 | }; 40 | printYulFunctionCall: PrintFunc = ({ path, print, node }) => { 41 | return [node.identifier, this.tuple(this.paramater(path.map(print, 'expressions')))]; 42 | }; 43 | printYulFunctionDefinition: PrintFunc = ({ node, path, print }) => { 44 | const parts: Doc[] = [ 45 | 'function', 46 | this.space, 47 | node.name!, 48 | this.tuple(this.paramater(node.parameters)), 49 | this.space, 50 | '->', 51 | this.space, 52 | this.paramater(node.returnParameters), 53 | this.space, 54 | path.call(print, 'body'), 55 | ]; 56 | return this.builders.group(parts); 57 | }; 58 | printYulIfStatement: PrintFunc = ({ path, print }) => { 59 | return ['if', this.space, path.call(print, 'condition'), this.space, path.call(print, 'body')]; 60 | }; 61 | printYulLiteral: PrintFunc = ({ node }) => node.name; 62 | printYulPath: PrintFunc = ({ node }) => node.name; 63 | printYulStatement: PrintFunc = ({ node, path, print }) => { 64 | const parts: Doc[] = []; 65 | if (typeof node.expression === 'string') { 66 | parts.push(node.expression); 67 | } else if (node.expression !== null) { 68 | parts.push(path.call(print as any, 'expression')); 69 | } 70 | return this.builders.group(parts, { shouldBreak: !!parts.length }); 71 | }; 72 | printYulSwitchCase: PrintFunc = ({ path, print }) => { 73 | return ['case', this.space, path.call(print, 'case'), this.space, path.call(print, 'body')]; 74 | }; 75 | printYulSwitchStatement: PrintFunc = ({ node, path, print }) => { 76 | const parts: Doc[] = ['switch', this.space, path.call(print, 'expression')]; 77 | path.map((p) => { 78 | parts.push(this.space, print(p)); 79 | }, 'switchCases'); 80 | if (node.default && node.body !== null) { 81 | parts.push(this.space, 'default', this.space, path.call(print, 'body')); 82 | } 83 | return this.builders.group(parts); 84 | }; 85 | printYulVariableDeclaration: PrintFunc = ({ node, path, print }) => { 86 | const parts: Doc[] = ['let']; 87 | parts.push(this.space, this.paramater(node.identifiers)); 88 | if (node.expression !== null) { 89 | parts.push(this.space, ':=', this.space, path.call(print, 'expression')); 90 | } 91 | return this.builders.group(parts); 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/tests/__files__/comment.txt: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.8.2 < 0.9.0; 3 | 4 | /// @title A simulator for trees 5 | /// @author Larry A. Gardner 6 | /// @notice You can use this contract for only the most basic simulation 7 | /// @dev All function calls are currently implemented without side effects 8 | /// @custom:experimental This is an experimental contract. 9 | contract Tree { 10 | /// @notice Calculate tree age in years, rounded up, for live trees 11 | /// @dev The Alexandr N. Tetearing algorithm could increase precision 12 | /// @param rings The number of rings from dendrochronological sample 13 | /// @return Age in years, rounded up for partial years 14 | function age(uint256 rings) external virtual pure returns (uint256) { 15 | return rings + 1; 16 | } 17 | 18 | ///@notice Returns the amount of leaves the tree has. 19 | /// @dev Returns only a fixed number. 20 | function leaves() external virtual pure returns(uint256) { 21 | return 2; 22 | } 23 | } 24 | 25 | contract Plant { 26 | function leaves() external virtual pure returns(uint256) { 27 | return 3; 28 | } 29 | } 30 | 31 | /** aabb */ 32 | contract KumquatTree is Tree, Plant /*test*/{ 33 | /** 34 | * @param _target Address of contract or account to call 35 | * @param _value Amount of ETH to send 36 | * @param _func Function signature, for example "foo(address,uint256)" 37 | * @param _data ABI encoded data send. 38 | * @param _timestamp Timestamp after which the transaction can be executed. 39 | */ 40 | function age(uint256 rings /* test */) external override pure returns (uint256) { 41 | 42 | //asdffff 43 | return rings + 2; 44 | } 45 | 46 | ///Return the amount of leaves that this specific kind of tree has 47 | /// @inheritdoc Tree 48 | function leaves() external override(Tree, Plant) pure returns(uint256) { 49 | return 3; 50 | } 51 | } 52 | 53 | contract A is B /*comment for B*/,C/*comment for C*/{} 54 | 55 | contract Comment { 56 | function test() pure { 57 | revert Transaction({ 58 | to: _to, 59 | value: _value, 60 | data: _data, 61 | executed: false, 62 | numConfirmations: 0 63 | }); 64 | } 65 | 66 | function aloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongName( 67 | string n1, 68 | string n2, 69 | string n3, 70 | string n4, 71 | string n5, 72 | string n6 73 | ) 74 | public 75 | pure 76 | modifier1() modifier22222222222222("asdfasfdasdfasdfasdf") 77 | virtual 78 | override(Foo, Bar, MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM) 79 | returns (string, string, string, string, string, string, string, string, string, string, string) 80 | { 81 | emit Test(); 82 | } 83 | 84 | function encode( 85 | uint x, 86 | address addr, 87 | uint[] calldata arr, 88 | MyStruct calldata myStruct 89 | ) external pure returns (bytes memory) { 90 | return abi.encode(x, addr, arr, myStruct); 91 | } 92 | 93 | function deploy(address _owner, uint _foo, bytes32 _salt) 94 | public 95 | payable 96 | returns (address) 97 | { 98 | // This syntax is a newer way to invoke create2 without assembly, you just need to pass salt 99 | // https://docs.soliditylang.org/en/latest/control-structures.html#salted-contract-creations-create2 100 | return address(new TestContract{ salt: _salt }(_owner, _foo)); 101 | } 102 | } 103 | 104 | function test() pure { 105 | // hhh11 106 | } 107 | 108 | abstract contract aloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongName is Foo("aloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongName", "aloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongName"), aloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongName2 {} 109 | 110 | contract Foo { 111 | // hhh 112 | } 113 | -------------------------------------------------------------------------------- /src/tests/__files__/meta.txt: -------------------------------------------------------------------------------- 1 | pragma solidity solidity ^0.8.20; 2 | pragma solidity solidity soliditysoliditysoliditysoliditysoliditysoliditysoliditysoliditysoliditysolidity ^0.8.20; 3 | 4 | import "./Foo.sol"; 5 | import * as Foo from "./Foo.sol"; 6 | import "./Foo.sol" as Foo; 7 | import { Foo, Bar as Me } from "./Foo.sol"; 8 | import { Foo, Bar as Me, Bar as Me, Bar as Me, Bar as Me, Bar as Me, Bar as Me } from "./Foo.sol"; 9 | 10 | enum Status { 11 | Pending 12 | } 13 | enum Status { 14 | Pending, 15 | Shipped, 16 | Accepted, 17 | Rejected, 18 | Pending, 19 | Shipped, 20 | Canceled 21 | } 22 | 23 | error NotOwnerError(); 24 | error AlreadyQueuedError(bytes32 txId); 25 | error TimestampNotInRangeError(uint blockTimestamp, uint timestamp); 26 | error TimestampExpiredError(uint blockTimestamp, uint expiresAt, uint expiresAt, uint expiresAt, uint expiresAt, uint expiresAt, uint expiresAt); 27 | 28 | event ChallengeExit(address indexed sender, uint); 29 | event Withdraw(address indexed to, uint amount, address indexed to, uint amount, address indexed, uint); 30 | 31 | using A for *; 32 | using A for B.C; 33 | using A for unit[] global; 34 | using { A, C.D } for B; 35 | using { B, add as + } for B; 36 | 37 | struct MyStruct { 38 | } 39 | struct MyStruct { 40 | string name; 41 | uint[2] nums; 42 | } 43 | 44 | type Foo is address; 45 | string constant boo = 'aaa'; 46 | 47 | contract TimestampNotInRangeErrorTimestampNotInRangeErrorTimestampNotInRangeError is FOo("foo"), X.Y { 48 | bool public boo = true; 49 | } 50 | 51 | contract h is FOo("foo"), X.Y, TimestampNotInRangeErrorTimestampNotInRangeErrorTimestampNotInRangeError { 52 | bool public boo = true; 53 | } 54 | -------------------------------------------------------------------------------- /src/tests/expression.test.ts: -------------------------------------------------------------------------------- 1 | import { createParse } from './utils.test'; 2 | import { test, expect } from 'vitest'; 3 | 4 | test('named-argument', () => { 5 | expect( 6 | createParse((p) => p.callArgumentList())(`({value: msg.value, bar: hex"aabb"})`), 7 | ).toMatchObject({ 8 | namedArguments: [ 9 | { 10 | type: 'NamedArgument', 11 | name: 'value', 12 | expression: { 13 | type: 'MemberAccess', 14 | expression: 'msg', 15 | memberName: 'value', 16 | }, 17 | }, 18 | { 19 | type: 'NamedArgument', 20 | name: 'bar', 21 | expression: { 22 | value: 'aabb', 23 | }, 24 | }, 25 | ], 26 | }); 27 | }); 28 | 29 | test('literal', () => { 30 | expect( 31 | createParse((p) => p.tupleExpression())( 32 | `(1, 0x1, 1 ether, "address", "", true, hex"ffff", unicode"😃")`, 33 | ), 34 | ).toMatchObject({ 35 | expressions: [ 36 | { 37 | type: 'NumberLiteral', 38 | value: '1', 39 | }, 40 | { 41 | type: 'NumberLiteral', 42 | hexValue: '0x1', 43 | }, 44 | { 45 | type: 'NumberLiteral', 46 | value: '1', 47 | subDenomination: 'ether', 48 | }, 49 | { 50 | type: 'StringLiteral', 51 | value: 'address', 52 | }, 53 | { 54 | type: 'StringLiteral', 55 | value: '', 56 | }, 57 | { 58 | type: 'BooleanLiteral', 59 | value: true, 60 | }, 61 | { 62 | type: 'HexStringLiteral', 63 | value: 'ffff', 64 | }, 65 | { 66 | type: 'UnicodeStringLiteral', 67 | value: '😃', 68 | }, 69 | ], 70 | }); 71 | }); 72 | 73 | test('assignment', () => { 74 | expect( 75 | createParse((p) => p.tupleExpression())(`(a += b, a++, delete b, foo ? x : 2)`), 76 | ).toMatchObject({ 77 | expressions: [ 78 | { 79 | left: 'a', 80 | right: 'b', 81 | operator: '+=', 82 | }, 83 | { 84 | operator: '++', 85 | left: 'a', 86 | }, 87 | { 88 | operator: 'delete', 89 | right: 'b', 90 | }, 91 | { 92 | condition: 'foo', 93 | trueExpression: 'x', 94 | falseExpression: { value: '2' }, 95 | }, 96 | ], 97 | }); 98 | }); 99 | 100 | test('memberAccess', () => { 101 | expect(createParse((p) => p.tupleExpression())(`(msg.value, foo.address)`)).toMatchObject({ 102 | expressions: [ 103 | { 104 | expression: 'msg', 105 | memberName: 'value', 106 | }, 107 | { 108 | expression: 'foo', 109 | memberName: 'address', 110 | }, 111 | ], 112 | }); 113 | }); 114 | 115 | test('expression', () => { 116 | expect( 117 | createParse((p) => p.tupleExpression())( 118 | `(call{foo: bar}, obj({foo: bar}), payable({foo: bar}), type(address), new Foo)`, 119 | ), 120 | ).toMatchObject({ 121 | expressions: [ 122 | { 123 | expression: 'call', 124 | namedArguments: [ 125 | { 126 | name: 'foo', 127 | expression: 'bar', 128 | }, 129 | ], 130 | }, 131 | { 132 | expression: 'obj', 133 | arguments: { 134 | namedArguments: [ 135 | { 136 | name: 'foo', 137 | expression: 'bar', 138 | }, 139 | ], 140 | }, 141 | }, 142 | { 143 | arguments: { 144 | namedArguments: [ 145 | { 146 | name: 'foo', 147 | expression: 'bar', 148 | }, 149 | ], 150 | }, 151 | }, 152 | { 153 | typeName: 'address', 154 | }, 155 | { 156 | typeName: 'Foo', 157 | }, 158 | ], 159 | }); 160 | }); 161 | 162 | test('indexAccess', () => { 163 | expect( 164 | createParse((p) => p.tupleExpression())(`(arr[0], arr[:10], arr[10:], arr[0:10])`), 165 | ).toMatchObject({ 166 | expressions: [ 167 | { 168 | baseExpression: 'arr', 169 | indexExpression: { value: '0' }, 170 | }, 171 | { 172 | baseExpression: 'arr', 173 | startExpression: null, 174 | endExpression: { value: '10' }, 175 | }, 176 | { 177 | baseExpression: 'arr', 178 | startExpression: { value: '10' }, 179 | endExpression: null, 180 | }, 181 | { 182 | baseExpression: 'arr', 183 | startExpression: { value: '0' }, 184 | endExpression: { value: '10' }, 185 | }, 186 | ], 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /src/tests/generator.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { parse, tokenizer } from '../parser'; 3 | import { generate } from '../generator'; 4 | 5 | test('generator', async () => { 6 | const code = ` 7 | 8 | pragma solidity solidity soliditysoliditysoliditysoliditysoliditysoliditysoliditysoliditysoliditysolidity ^0.8.20; 9 | 10 | 11 | contract Hi { 12 | struct MyStruct { 13 | string name; 14 | uint[2] nums; 15 | } 16 | } 17 | 18 | // asdf 19 | `; 20 | 21 | const ast = parse(code, { tolerant: true }); 22 | const tokens = tokenizer(code, { tolerant: true }); 23 | // @ts-ignore 24 | const target = ast.nodes[1].nodes[0]; 25 | (target).comments = tokens.filter((token) => token.type?.includes('COMMENT')); 26 | console.log(await generate(target)); 27 | }); 28 | -------------------------------------------------------------------------------- /src/tests/meta.test.ts: -------------------------------------------------------------------------------- 1 | import { createParse } from './utils.test'; 2 | import { test, expect } from 'vitest'; 3 | 4 | test('sourceUnit', () => { 5 | expect(createParse((p) => p.sourceUnit())(`// SPDX-License-Identifier: MIT`)).toMatchObject({ 6 | type: 'SourceUnit', 7 | }); 8 | }); 9 | 10 | test('pragmaDirective', () => { 11 | expect(createParse((p) => p.pragmaDirective())(`pragma solidity ^0.8.24;`)).toMatchObject({ 12 | literals: ['solidity', '^0.8.24'], 13 | }); 14 | expect(createParse((p) => p.pragmaDirective())(`pragma version >=0.8.24;`)).toMatchObject({ 15 | literals: ['version', '>=0.8.24'], 16 | }); 17 | }); 18 | 19 | test('usingDirective', () => { 20 | expect(createParse((p) => p.usingDirective())(`using A for uint;`)).toMatchObject({ 21 | libraryName: 'A', 22 | typeName: 'uint', 23 | }); 24 | expect(createParse((p) => p.usingDirective())(`using A for B.C;`)).toMatchObject({ 25 | libraryName: 'A', 26 | typeName: 'B.C', 27 | }); 28 | expect(createParse((p) => p.usingDirective())(`using A for unit[] global;`)).toMatchObject({ 29 | libraryName: 'A', 30 | typeName: 'unit[]', 31 | global: true, 32 | }); 33 | expect(createParse((p) => p.usingDirective())(`using { A, C.D } for B;`)).toMatchObject({ 34 | usingAliases: [{ name: 'A' }, { name: 'C.D' }], 35 | typeName: 'B', 36 | global: false, 37 | }); 38 | expect(createParse((p) => p.usingDirective())(`using { B, add as + } for B;`)).toMatchObject({ 39 | usingAliases: [{ name: 'B' }, { name: 'add', operator: '+' }], 40 | typeName: 'B', 41 | global: false, 42 | }); 43 | }); 44 | 45 | test('importDirective', () => { 46 | expect(createParse((p) => p.importDirective())(`import "./Foo.sol";`)).toMatchObject({ 47 | path: './Foo.sol', 48 | }); 49 | expect( 50 | createParse((p) => p.importDirective())(`import * as Foo from "./Foo.sol";`), 51 | ).toMatchObject({ 52 | importAll: true, 53 | unitAlias: 'Foo', 54 | symbolAliases: [], 55 | }); 56 | expect(createParse((p) => p.importDirective())(`import "./Foo.sol" as Foo;`)).toMatchObject({ 57 | importAll: false, 58 | unitAlias: 'Foo', 59 | symbolAliases: [], 60 | }); 61 | expect( 62 | createParse((p) => p.importDirective())(`import { Foo, Bar as Me } from "./Foo.sol";`), 63 | ).toMatchObject({ 64 | symbolAliases: [ 65 | { 66 | foreign: 'Foo', 67 | }, 68 | { 69 | foreign: 'Bar', 70 | local: 'Me', 71 | }, 72 | ], 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/tests/parser.test.ts: -------------------------------------------------------------------------------- 1 | import { parse, tokenizer } from '../parser'; 2 | import { test, expect } from 'vitest'; 3 | 4 | test('parser', () => { 5 | const ast = parse(`// SPDX-License-Identifier: MIT 6 | // compiler version must be greater than or equal to 0.8.20 and less than 0.9.0 7 | pragma solidity ^0.8.20; 8 | 9 | contract HelloWorld { 10 | string public greet = "Hello World!"; 11 | }`); 12 | 13 | expect(ast.type).toBe('SourceUnit'); 14 | 15 | expect( 16 | tokenizer(`// SPDX-License-Identifier: MIT 17 | // compiler version must be greater than or equal to 0.8.20 and less than 0.9.0 18 | pragma solidity ^0.8.20; 19 | contract HelloWorld { 20 | string public greet = "Hello World!"; 21 | }`).length, 22 | ).toBe(15); 23 | 24 | expect(() => parse('contract 1fOO {}')).toThrow(); 25 | expect(() => parse('asdfalsdkfjasdlfjasdfadsf')).toThrow(); 26 | expect(() => parse('contract HelloW', { tolerant: false })).toThrow(); 27 | 28 | expect(() => tokenizer('contract 1fOO {}')).not.toThrow(); 29 | expect(() => tokenizer('asdfalsdkfjasdlfjasdfadsf', { tolerant: true })).not.toThrow(); 30 | }); 31 | -------------------------------------------------------------------------------- /src/tests/statement.test.ts: -------------------------------------------------------------------------------- 1 | import { createParse } from './utils.test'; 2 | import { test, expect } from 'vitest'; 3 | 4 | test('ifStatement', () => { 5 | expect(createParse((p) => p.ifStatement())(`if (foo) {} else if (bar) {} else {}`)).toMatchObject( 6 | { 7 | condition: 'foo', 8 | ifStatement: {}, 9 | elseStatement: { 10 | condition: 'bar', 11 | ifStatement: {}, 12 | elseStatement: {}, 13 | }, 14 | }, 15 | ); 16 | }); 17 | 18 | test('revertStatement', () => { 19 | expect(createParse((p) => p.revertStatement())(`revert Foo(name);`)).toMatchObject({ 20 | expression: 'Foo', 21 | arguments: { expressions: ['name'] }, 22 | }); 23 | }); 24 | 25 | test('returnStatement', () => { 26 | expect(createParse((p) => p.returnStatement())(`return name;`)).toMatchObject({ 27 | expression: 'name', 28 | }); 29 | }); 30 | 31 | test('emitStatement', () => { 32 | expect(createParse((p) => p.emitStatement())(`emit Transfer(name);`)).toMatchObject({ 33 | expression: 'Transfer', 34 | arguments: { expressions: ['name'] }, 35 | }); 36 | }); 37 | 38 | test('assemblyStatement', () => { 39 | expect( 40 | createParse((p) => p.assemblyStatement())(`assembly "evmasm" ("foo", "bar") {}`), 41 | ).toMatchObject({ 42 | flags: ['foo', 'bar'], 43 | dialect: 'evmasm', 44 | }); 45 | }); 46 | 47 | test('tryStatement', () => { 48 | expect( 49 | createParse((p) => p.tryStatement())( 50 | `try new Foo(owner) returns (Foo foo) {} catch Error(string memory reason) {} catch (bytes memory reason) {}`, 51 | ), 52 | ).toMatchObject({ 53 | expression: { expression: { typeName: 'Foo' }, arguments: { expressions: ['owner'] } }, 54 | returnParameters: [{ name: 'foo', typeName: 'Foo' }], 55 | body: {}, 56 | catchClauses: [ 57 | { 58 | errorName: 'Error', 59 | parameters: [{ name: 'reason', typeName: 'string', dataLocation: 'memory' }], 60 | body: {}, 61 | }, 62 | { 63 | parameters: [{ name: 'reason', typeName: 'bytes', dataLocation: 'memory' }], 64 | body: {}, 65 | }, 66 | ], 67 | }); 68 | }); 69 | 70 | test('whileStatement', () => { 71 | expect(createParse((p) => p.whileStatement())(`while (foo) {}`)).toMatchObject({ 72 | condition: 'foo', 73 | }); 74 | expect(createParse((p) => p.doWhileStatement())(`do {} while (foo);`)).toMatchObject({ 75 | condition: 'foo', 76 | }); 77 | }); 78 | 79 | test('forStatement', () => { 80 | expect(createParse((p) => p.forStatement())(`for (uint i = 0; i < 10; i++) {}`)).toMatchObject({ 81 | initializationExpression: { type: 'VariableDeclarationStatement' }, 82 | conditionExpression: { expression: { operator: '<', left: 'i', right: { value: '10' } } }, 83 | loopExpression: { operator: '++', left: 'i' }, 84 | }); 85 | }); 86 | 87 | test('block', () => { 88 | expect( 89 | createParse((p) => p.block())(`{ break; continue; unchecked { return; } { continue; } }`), 90 | ).toMatchObject({ 91 | statements: [ 92 | 'break', 93 | 'continue', 94 | { 95 | unchecked: true, 96 | statements: [{}], 97 | }, 98 | { 99 | unchecked: false, 100 | statements: ['continue'], 101 | }, 102 | ], 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /src/tests/traverse.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { parse } from '../parser'; 3 | import { 4 | createSelector, 5 | query, 6 | querySelector, 7 | querySelectorAll, 8 | serialize, 9 | traverse, 10 | visit, 11 | visitNodes, 12 | } from '../traverse'; 13 | 14 | test('traverse', () => { 15 | const ast = parse(`// SPDX-License-Identifier: MIT 16 | // compiler version must be greater than or equal to 0.8.20 and less than 0.9.0 17 | pragma solidity ^0.8.20; 18 | 19 | contract HelloWorld { 20 | string public greet = "Hello World!"; 21 | }`); 22 | 23 | const enterNames: string[] = []; 24 | const exitNames: string[] = []; 25 | 26 | visit(ast, { 27 | enter: ({ node }) => { 28 | if (node.type === 'ContractDefinition') { 29 | enterNames.push(node.name.name); 30 | } 31 | }, 32 | exit: ({ node }) => { 33 | if (node.type === 'ContractDefinition') { 34 | exitNames.push(node.name.name); 35 | } 36 | }, 37 | }); 38 | 39 | expect(enterNames).toEqual(['HelloWorld']); 40 | expect(exitNames).toEqual(['HelloWorld']); 41 | 42 | // @ts-expect-error 43 | expect(serialize(ast, (p) => ({ ...p.node, _flag: true }))._flag).toEqual(true); 44 | 45 | traverse(ast, (p) => { 46 | if (p.matches({ name: 'greet' })) { 47 | expect(p.node.type).toBe('Identifier'); 48 | } 49 | }); 50 | 51 | traverse(ast, (p) => { 52 | if (p.node.type === 'ContractDefinition') { 53 | expect(p.getFlattenParents().length).toBe(1); 54 | expect(p.getFlattenParents(1).length).toBe(1); 55 | } 56 | }); 57 | 58 | expect(visitNodes(ast, (p) => p.depth === 1).length).toBe(2); 59 | }); 60 | 61 | test('selector', () => { 62 | const ast = parse(`// SPDX-License-Identifier: MIT 63 | // compiler version must be greater than or equal to 0.8.20 and less than 0.9.0 64 | pragma solidity ^0.8.20; 65 | 66 | contract HelloWorld { 67 | string public greet = "Hello World!"; 68 | }`); 69 | 70 | expect(querySelector(ast, createSelector('*'))!.node).toMatchObject({ type: 'SourceUnit' }); 71 | expect(querySelectorAll(ast, createSelector('*')).length).toBe(8); 72 | 73 | expect( 74 | querySelectorAll(ast, createSelector('ContractDefinition').child('Identifier')), 75 | ).toMatchObject([{ node: { type: 'Identifier', name: 'HelloWorld' } }]); 76 | expect( 77 | querySelectorAll(ast, createSelector('ContractDefinition').inside('Identifier')), 78 | ).toMatchObject([ 79 | { node: { type: 'Identifier', name: 'HelloWorld' } }, 80 | { node: { type: 'Identifier', name: 'greet' } }, 81 | ]); 82 | expect( 83 | query(ast, createSelector('ContractDefinition').inside('Identifier'), { 84 | queryAll: true, 85 | order: 'desc', 86 | }), 87 | ).toMatchObject([ 88 | { node: { type: 'Identifier', name: 'greet' } }, 89 | { node: { type: 'Identifier', name: 'HelloWorld' } }, 90 | ]); 91 | 92 | expect( 93 | querySelectorAll(ast, [createSelector('ContractDefinition'), createSelector('PragmaDirective')]) 94 | .length, 95 | ).toBe(2); 96 | 97 | expect(querySelector(ast, createSelector('ContractDefinition', 1))).toBe(null); 98 | expect(querySelector(ast, createSelector('ContractDefinition', 139))).toMatchObject({ 99 | node: { 100 | type: 'ContractDefinition', 101 | }, 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /src/tests/type.test.ts: -------------------------------------------------------------------------------- 1 | import { createParse } from './utils.test'; 2 | import { test, expect } from 'vitest'; 3 | 4 | test('typeName', () => { 5 | expect(createParse((p) => p.typeName())(`address`)).toBe('address'); 6 | expect(createParse((p) => p.typeName())(`bytes`)).toBe('bytes'); 7 | expect( 8 | createParse((p) => p.typeName())(`function(address user) public pure returns(bool)`), 9 | ).toMatchObject({ 10 | visibility: 'public', 11 | stateMutability: 'pure', 12 | parameters: [{ name: 'user', typeName: 'address' }], 13 | returnParameters: [{ name: null, typeName: 'bool' }], 14 | }); 15 | expect(createParse((p) => p.typeName())(`mapping(address => uint)`)).toMatchObject({ 16 | keyType: 'address', 17 | valueType: 'uint', 18 | }); 19 | 20 | expect(createParse((p) => p.elementaryTypeName(true))(`address payable`)).toBe('address'); 21 | expect(createParse((p) => p.typeName())(`uint128[]`)).toBe('uint128[]'); 22 | }); 23 | -------------------------------------------------------------------------------- /src/tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CharStreams, 3 | SolidityLexer, 4 | SolidityParser, 5 | CommonTokenStream, 6 | ParserRuleContext, 7 | } from '../antlr4'; 8 | import { SyntaxNode } from '../ast'; 9 | import { solidityASTBuilder } from '../ast/builder'; 10 | import { serialize } from '../traverse'; 11 | import { BaseNodeString } from '../ast/base'; 12 | import { test } from 'vitest'; 13 | 14 | export const format = (ast: SyntaxNode) => 15 | serialize(ast, (p) => { 16 | if (p.node instanceof BaseNodeString) return p.node.name; 17 | return p.node; 18 | }); 19 | 20 | export const parse = ( 21 | input: string, 22 | callback: (parser: SolidityParser) => ParserRuleContext = (p) => p.sourceUnit(), 23 | afterParse: (ast: SyntaxNode) => any = format, 24 | ) => { 25 | const lexer = new SolidityLexer(CharStreams.fromString(input)); 26 | const parser = new SolidityParser(new CommonTokenStream(lexer)); 27 | const tree = callback(parser); 28 | const ast = tree.accept(solidityASTBuilder)!; 29 | return afterParse(ast); 30 | }; 31 | 32 | export const createParse = ( 33 | callback: (parser: SolidityParser) => ParserRuleContext = (p) => p.sourceUnit(), 34 | afterParse: (ast: SyntaxNode) => any = format, 35 | ) => { 36 | return (input: string) => parse(input, callback, afterParse); 37 | }; 38 | 39 | export const createLog = ( 40 | callback: (parser: SolidityParser) => ParserRuleContext = (p) => p.sourceUnit(), 41 | ) => { 42 | return (input: string) => 43 | parse(input, callback, (ast) => { 44 | const newAST = serialize(ast, (p) => { 45 | if (p.node instanceof BaseNodeString) { 46 | return p.node.name; 47 | } 48 | return p.node; 49 | }); 50 | console.log(newAST); 51 | return newAST; 52 | }); 53 | }; 54 | 55 | export const visitor = solidityASTBuilder; 56 | 57 | test('utils', () => {}); 58 | -------------------------------------------------------------------------------- /src/tests/yul.test.ts: -------------------------------------------------------------------------------- 1 | import { createParse } from './utils.test'; 2 | import { test, expect } from 'vitest'; 3 | 4 | test('assemblyStatement', () => { 5 | expect(createParse((p) => p.assemblyStatement())(`assembly { let x := 123 }`)).toMatchObject({ 6 | yulStatements: [{ expression: { type: 'YulVariableDeclaration', identifiers: ['x'] } }], 7 | }); 8 | expect( 9 | createParse((p) => p.assemblyStatement())(`assembly { f.b, foo, bar := mul(x, y) }`), 10 | ).toMatchObject({ 11 | yulStatements: [{ expression: { type: 'YulAssignment', paths: ['f.b', 'foo', 'bar'] } }], 12 | }); 13 | expect(createParse((p) => p.assemblyStatement())(`assembly { mul(x, y) }`)).toMatchObject({ 14 | yulStatements: [ 15 | { expression: { type: 'YulFunctionCall', identifier: 'mul', expressions: ['x', 'y'] } }, 16 | ], 17 | }); 18 | expect(createParse((p) => p.assemblyStatement())(`assembly { if foo {} }`)).toMatchObject({ 19 | yulStatements: [{ expression: { type: 'YulIfStatement', condition: 'foo', body: {} } }], 20 | }); 21 | expect(createParse((p) => p.assemblyStatement())(`assembly { for {} bar {} {} }`)).toMatchObject({ 22 | yulStatements: [ 23 | { 24 | expression: { 25 | type: 'YulForStatement', 26 | initializationBlock: {}, 27 | conditionExpression: 'bar', 28 | loopBlock: {}, 29 | body: {}, 30 | }, 31 | }, 32 | ], 33 | }); 34 | expect( 35 | createParse((p) => p.assemblyStatement())(`assembly { leave break continue }`), 36 | ).toMatchObject({ 37 | yulStatements: [{ expression: 'leave' }, { expression: 'break' }, { expression: 'continue' }], 38 | }); 39 | expect( 40 | createParse((p) => p.assemblyStatement())(`assembly { switch x case 0 {} default {} }`), 41 | ).toMatchObject({ 42 | yulStatements: [ 43 | { 44 | expression: { 45 | type: 'YulSwitchStatement', 46 | expression: 'x', 47 | switchCases: [{ case: '0' }], 48 | default: true, 49 | body: {}, 50 | }, 51 | }, 52 | ], 53 | }); 54 | expect( 55 | createParse((p) => p.assemblyStatement())(`assembly { function foo(x, y) -> a, b {} }`), 56 | ).toMatchObject({ 57 | yulStatements: [ 58 | { 59 | expression: { 60 | type: 'YulFunctionDefinition', 61 | name: 'foo', 62 | parameters: ['x', 'y'], 63 | returnParameters: ['a', 'b'], 64 | body: {}, 65 | }, 66 | }, 67 | ], 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/traverse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './selector'; 2 | export * from './serialize'; 3 | export * from './traverse'; 4 | export * from './visit'; 5 | -------------------------------------------------------------------------------- /src/traverse/selector.ts: -------------------------------------------------------------------------------- 1 | import { PartialDeep } from 'type-fest'; 2 | import { SyntaxNode, SyntaxNodeType } from '../ast'; 3 | import { TraversePath, traverse } from './traverse'; 4 | 5 | export interface QueryOptions { 6 | // return all matched nodes when `true` 7 | queryAll?: boolean; 8 | // asc: ascending, depth from 0 to n 9 | // desc: descending, depth from n to 0 10 | // default: asc 11 | order?: 'asc' | 'desc'; 12 | } 13 | 14 | export enum SelectorCombinator { 15 | Child = '>', 16 | Inside = ' ', 17 | // After = '+', 18 | // Before = '~', 19 | } 20 | 21 | const getPathKey = (path: TraversePath) => `${path.path}#${JSON.stringify(path.node)}`; 22 | 23 | export type SelectorFilter = '*' | SyntaxNodeType | PartialDeep; 24 | 25 | export class Selector { 26 | public static create = (filter: SelectorFilter, _offset?: number): Selector => { 27 | let target: PartialDeep = {}; 28 | if (typeof filter !== 'string') { 29 | target = filter; 30 | } else if (filter === '*') { 31 | target = {}; 32 | } else { 33 | target = { type: filter as any }; 34 | } 35 | return new Selector(target, _offset); 36 | }; 37 | 38 | public next?: Selector; 39 | public combinator?: SelectorCombinator; 40 | 41 | private constructor( 42 | public filter: PartialDeep, 43 | public _offset?: number, 44 | ) {} 45 | 46 | public _prev?: Selector; 47 | public _getStartSelector = (): Selector => { 48 | return this._prev ? this._prev._getStartSelector() : this; 49 | }; 50 | 51 | private chain = ( 52 | combinator: SelectorCombinator, 53 | f: SelectorFilter, 54 | _offset?: number, 55 | ): Selector => { 56 | this.combinator = combinator; 57 | const next = Selector.create(f, _offset || this._offset); 58 | next._prev = this; 59 | this.next = next; 60 | return next; 61 | }; 62 | 63 | public child = (f: SelectorFilter, _offset?: number): Selector => { 64 | return this.chain(SelectorCombinator.Child, f, _offset); 65 | }; 66 | 67 | public inside = (f: SelectorFilter, _offset?: number): Selector => { 68 | return this.chain(SelectorCombinator.Inside, f, _offset); 69 | }; 70 | 71 | /** @inner */ 72 | public query = ( 73 | node: SyntaxNode | null, 74 | options: QueryOptions = {}, 75 | ): Record> => { 76 | if (!node) return {}; 77 | const result: Record> = {}; 78 | traverse(node, (path) => { 79 | if (path.matches(this.filter) && path.checkOffset(this._offset)) { 80 | Object.assign(result, this.recursion(path, options)); // entry 81 | } 82 | }); 83 | return result; 84 | }; 85 | 86 | /** @inner */ 87 | public recursion = ( 88 | path: TraversePath, 89 | options: QueryOptions = {}, 90 | ): Record> => { 91 | if (!path.matches(this.filter)) return {}; 92 | if (!path.checkOffset(this._offset)) return {}; 93 | 94 | const result: Record> = {}; 95 | if (!this.next) { 96 | result[getPathKey(path)] = path as any; 97 | return result; 98 | } 99 | 100 | if (this.combinator === SelectorCombinator.Child) { 101 | traverse(path, (p) => { 102 | if (p.depth - path.depth > 1) p.stop(); 103 | Object.assign(result, this.next!.recursion(p, options)); 104 | }); 105 | } else if (this.combinator === SelectorCombinator.Inside) { 106 | traverse(path, (p) => { 107 | Object.assign(result, this.next!.recursion(p, options)); 108 | }); 109 | } 110 | 111 | return result; 112 | }; 113 | } 114 | 115 | export const createSelector = Selector.create; 116 | 117 | export const query = ( 118 | ast: SyntaxNode | null, 119 | selector: Selector | Selector[], 120 | options: QueryOptions = {}, 121 | ): TraversePath[] => { 122 | if (!ast) return []; 123 | 124 | const result: Record> = {}; 125 | const selectors = Array.isArray(selector) ? selector : [selector]; 126 | 127 | for (let index = 0; index < selectors.length; index += 1) { 128 | const current = selectors[index]; 129 | const startSelector = current._getStartSelector(); 130 | Object.assign(result, startSelector.query(ast, options)); 131 | } 132 | return Object.values(result).sort((current, next) => { 133 | if (options.order === 'desc') { 134 | return next.depth - current.depth; 135 | } 136 | return current.depth - next.depth; 137 | }); 138 | }; 139 | 140 | export const querySelector = ( 141 | ast: SyntaxNode | null, 142 | selector: Selector | Selector[], 143 | ): TraversePath | null => { 144 | return query(ast, selector, { queryAll: false })?.[0] ?? null; 145 | }; 146 | 147 | export const querySelectorAll = ( 148 | ast: SyntaxNode | null, 149 | selector: Selector | Selector[], 150 | ): TraversePath[] => { 151 | return query(ast, selector, { queryAll: true }); 152 | }; 153 | -------------------------------------------------------------------------------- /src/traverse/serialize.ts: -------------------------------------------------------------------------------- 1 | import { SyntaxNode } from '../ast'; 2 | import { TraversePath, traverse } from './traverse'; 3 | 4 | export type SerializeHandler = (path: TraversePath) => any; 5 | 6 | export const serialize = ( 7 | _ast: T, 8 | handler?: SerializeHandler, 9 | ): T => { 10 | return traverse(_ast, (p) => { 11 | if (handler) p.rewrite(handler(p)); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/traverse/traverse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const */ 2 | import { clone, matches as lodashMatches } from 'lodash-es'; 3 | import { SyntaxNode } from '../ast'; 4 | import { isSyntaxNode, isSyntaxNodeList, keysInNode } from '../ast/base'; 5 | import { PartialDeep } from 'type-fest'; 6 | 7 | export type TraverseMatchFilter = PartialDeep; 8 | 9 | export interface TraversePath { 10 | /** 11 | * The key of the node 12 | * @example "SourceUnit.ContractDefinition" 13 | */ 14 | path: string; 15 | /** 16 | * The depth of the AST, starting from 0 17 | */ 18 | depth: number; 19 | /** 20 | * Syntax Node form AST 21 | */ 22 | node: N; 23 | /** 24 | * The parent path 25 | */ 26 | parentPath: TraversePath | null; 27 | /** 28 | * Stop traversing 29 | */ 30 | stop: () => void; 31 | /** 32 | * Rewrite the current node 33 | * @param value The new value to replace the current node 34 | */ 35 | rewrite: (value: SyntaxNode | any) => void; 36 | /** 37 | * Is the current node matches the filter 38 | * @param filter The filter to match 39 | */ 40 | matches: (filter: TraverseMatchFilter) => boolean; 41 | /** 42 | * Check if the current node contains the offset 43 | * @param offset 44 | * @returns 45 | */ 46 | checkOffset: (offset?: number) => boolean; 47 | /** 48 | * Get the flatten node list 49 | */ 50 | getFlattenParents: (maxDepth?: number) => N[]; 51 | } 52 | 53 | export type TraverseCallback = (path: TraversePath) => void | (() => void); 54 | 55 | /** 56 | * Traverse 57 | * @param ast SyntaxNode Tree 58 | * @param callback like Array.map, but it will traverse the tree 59 | * @returns The new AST, the original AST will not be modified 60 | */ 61 | export function traverse( 62 | ast: T | TraversePath, 63 | callback: TraverseCallback, 64 | ): T; 65 | export function traverse(astOrPath: any, callback: TraverseCallback): T { 66 | let ast: T; 67 | let globalParentPath: TraversePath | null = null; 68 | if (astOrPath.type && !astOrPath.matches) { 69 | ast = astOrPath; 70 | } else { 71 | ast = astOrPath.node; 72 | globalParentPath = astOrPath; 73 | } 74 | 75 | let shouldStop: boolean = false; 76 | const stop = () => { 77 | shouldStop = true; 78 | }; 79 | 80 | // inner recursion function 81 | const traverseInner = ( 82 | tree: U | U[], 83 | parentPath: TraversePath | null, 84 | ): U | U[] => { 85 | const depth = parentPath?.depth !== undefined ? parentPath.depth + 1 : 0; 86 | if (isSyntaxNodeList(tree)) { 87 | // Map format node list 88 | const nodeList: U[] = []; 89 | for (let index = 0; index < tree.length; index += 1) { 90 | if (shouldStop) break; 91 | nodeList.push(traverseInner(tree[index] as any, parentPath)); 92 | } 93 | return nodeList; 94 | } else if (isSyntaxNode(tree)) { 95 | let node = clone(tree); 96 | const rewrite: TraversePath['rewrite'] = (newNode: any) => { 97 | node = newNode; 98 | }; 99 | const matches: TraversePath['matches'] = (filter) => lodashMatches(filter)(node); 100 | const getFlattenParents: TraversePath['getFlattenParents'] = ( 101 | maxDepth = Number.MAX_SAFE_INTEGER, 102 | ) => { 103 | const nodes: any[] = []; 104 | const recursion = (p: TraversePath) => { 105 | if (p.parentPath && nodes.length < maxDepth) { 106 | nodes.unshift(p.parentPath.node); 107 | recursion(p.parentPath); 108 | } 109 | }; 110 | // eslint-disable-next-line @typescript-eslint/no-use-before-define 111 | recursion(path); 112 | return nodes; 113 | }; 114 | const checkOffset: TraversePath['checkOffset'] = (offset) => { 115 | if (offset === undefined) return true; 116 | return node.range[0] <= offset && offset <= node.range[1]; 117 | }; 118 | 119 | const path: TraversePath = { 120 | path: [parentPath?.path, node.type].filter((t) => t !== undefined && t !== null).join('.'), 121 | depth, 122 | node, 123 | parentPath, 124 | stop, 125 | rewrite, 126 | getFlattenParents, 127 | matches, 128 | checkOffset, 129 | }; 130 | 131 | const exitCallback = callback(path); 132 | 133 | if (shouldStop) return node; 134 | if (!isSyntaxNode(node)) return node; 135 | 136 | const keys = keysInNode(node); 137 | for (let index = 0; index < keys.length; index += 1) { 138 | const valueInKey = node[keys[index]]; 139 | if (isSyntaxNode(valueInKey) || isSyntaxNodeList(valueInKey)) { 140 | node[keys[index]] = traverseInner(valueInKey as any, path); 141 | } else { 142 | node[keys[index]] = valueInKey; 143 | } 144 | } 145 | 146 | // Exit callback executes after all 147 | if (exitCallback && typeof exitCallback === 'function') exitCallback(); 148 | return node; 149 | } 150 | return tree; 151 | }; 152 | return traverseInner(ast, globalParentPath) as T; 153 | } 154 | -------------------------------------------------------------------------------- /src/traverse/visit.ts: -------------------------------------------------------------------------------- 1 | import { LookUp } from '../ast/base'; 2 | import { SyntaxNode, SyntaxNodeType } from '../ast'; 3 | import { TraversePath, traverse } from './traverse'; 4 | 5 | export type VisitListener = (path: TraversePath) => void; 6 | 7 | export type VisitHandlers = { 8 | [K in SyntaxNodeType]?: VisitListener>; 9 | } & { 10 | [K in SyntaxNodeType as `exit${K}`]?: VisitListener>; 11 | } & { 12 | enter?: VisitListener; 13 | exit?: VisitListener; 14 | }; 15 | 16 | export const visit = (ast: T, handlers: VisitHandlers): void => { 17 | traverse(ast, (p) => { 18 | const nodeType = p.node.type; 19 | handlers.enter?.(p); 20 | handlers[nodeType]?.(p as any); 21 | handlers[`exit${nodeType}`]?.(p as any); 22 | handlers.exit?.(p); 23 | }); 24 | }; 25 | 26 | export const visitNodes = ( 27 | ast: SyntaxNode, 28 | callback: (p: TraversePath) => boolean, 29 | ): T[] => { 30 | const nodes: T[] = []; 31 | visit(ast, { 32 | enter(path) { 33 | if (callback(path)) nodes.push(path.node as any); 34 | }, 35 | }); 36 | return nodes; 37 | }; 38 | -------------------------------------------------------------------------------- /src/typedoc.ts: -------------------------------------------------------------------------------- 1 | export * from './ast/index.node'; 2 | export * from './ast/base'; 3 | export { SyntaxNode, SyntaxNodeType } from './ast'; 4 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | // reporters: ['default', 'html'], 6 | coverage: { 7 | include: ['src/**/*.ts'], 8 | exclude: ['node_modules', 'dist', '.idea', '.git', '.cache', 'src/antlr4', 'docs'], 9 | }, 10 | }, 11 | }); 12 | --------------------------------------------------------------------------------