├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── nodejs-pr.yml │ └── nodejs.yml ├── .gitignore ├── .gitpod.yml ├── .madrun.js ├── .npmignore ├── .npmrc ├── .nycrc.json ├── .putout.json ├── .typos.toml ├── ChangeLog ├── LICENSE ├── README.md ├── bin └── gs.js ├── eslint.config.js ├── examples ├── 1.gs ├── 1.js ├── 2.gs └── 2.js ├── package.json ├── packages ├── convert │ ├── add-array │ │ └── index.js │ ├── apply-if-let │ │ └── index.js │ ├── apply-try │ │ └── index.js │ ├── index.js │ ├── index.spec.js │ └── remove-import-try │ │ ├── fixture │ │ ├── try-fix.js │ │ └── try.js │ │ ├── index.js │ │ └── index.spec.js ├── goldstein │ ├── fixes │ │ └── hashbang.js │ ├── index.js │ ├── index.spec.js │ └── parser.js ├── internal-parse-maybe-assign │ ├── fixture │ │ ├── add-array.gs │ │ └── assign-from.gs │ ├── index.js │ └── index.spec.js ├── keyword-add-array │ ├── fixture │ │ ├── add-array.gs │ │ ├── add-array.js │ │ ├── assign-from.gs │ │ ├── assign-from.js │ │ ├── const.gs │ │ └── yield.gs │ ├── index.js │ └── index.spec.js ├── keyword-arrow │ ├── fixture │ │ ├── arrow.gs │ │ └── arrow.js │ ├── index.js │ └── index.spec.js ├── keyword-assign-from │ ├── fixture │ │ ├── add-array.gs │ │ ├── assign-from.gs │ │ ├── assign-from.js │ │ └── const.gs │ ├── index.js │ └── index.spec.js ├── keyword-broken-string │ ├── fixture │ │ ├── broken-string.gs │ │ ├── broken-string.js │ │ ├── infinite.gs │ │ ├── infinite.js │ │ ├── mobile-quote-close.gs │ │ ├── mobile-quote-close.js │ │ ├── mobile-quote-open.gs │ │ ├── mobile-quote-open.js │ │ ├── mobile-quote.gs │ │ ├── mobile-quote.js │ │ ├── no-semi.gs │ │ └── no-semi.js │ ├── index.js │ └── index.spec.js ├── keyword-curry │ ├── fixture │ │ ├── curry.gs │ │ ├── curry.js │ │ └── raise.gs │ ├── index.js │ └── index.spec.js ├── keyword-export-no-const │ ├── fixture │ │ ├── export-no-const.gs │ │ └── export-no-const.js │ ├── index.js │ └── index.spec.js ├── keyword-fn │ ├── fixture │ │ ├── export.gs │ │ ├── export.js │ │ ├── fn-function.gs │ │ ├── fn.gs │ │ ├── fn.js │ │ ├── other-export.gs │ │ └── other-export.js │ ├── index.js │ └── index.spec.js ├── keyword-freeze │ ├── fixture │ │ ├── array-freeze.gs │ │ ├── array-freeze.js │ │ ├── freeze.gs │ │ ├── freeze.js │ │ └── not-supported.gs │ ├── index.js │ └── index.spec.js ├── keyword-guard │ ├── fixture │ │ ├── diff-parens.gs │ │ ├── guard.gs │ │ ├── guard.js │ │ ├── no-parens.gs │ │ └── no-parens.js │ ├── index.js │ └── index.spec.js ├── keyword-if │ ├── fixture │ │ ├── diff-parens.gs │ │ ├── if-let.gs │ │ ├── if-let.js │ │ ├── if.gs │ │ ├── if.js │ │ └── no-brace.gs │ ├── index.js │ └── index.spec.js ├── keyword-import │ ├── fixture │ │ ├── import-identifier.gs │ │ ├── import-identifier.js │ │ ├── import.gs │ │ ├── import.js │ │ ├── wrong-brace.gs │ │ └── wrong-brace.js │ ├── index.js │ └── index.spec.js ├── keyword-missing-initializer │ ├── fixture │ │ ├── missing-initializer.gs │ │ ├── missing-initializer.js │ │ └── no-safe-assignment.gs │ ├── index.js │ └── index.spec.js ├── keyword-should │ ├── fixture │ │ ├── await-should.gs │ │ ├── await-should.js │ │ ├── not-supported.gs │ │ ├── should.gs │ │ └── should.js │ ├── index.js │ └── index.spec.js ├── keyword-throw │ ├── fixture │ │ ├── throw-statement.gs │ │ ├── throw-statement.js │ │ ├── throw.gs │ │ └── throw.js │ ├── index.js │ └── index.spec.js ├── keyword-try │ ├── fixture │ │ ├── await.gs │ │ ├── await.js │ │ ├── fn.gs │ │ ├── fn.js │ │ ├── no-catch.gs │ │ ├── not-supported.gs │ │ ├── try-catch.gs │ │ ├── try-catch.js │ │ ├── try.gs │ │ ├── try.js │ │ ├── var.gs │ │ └── var.js │ ├── index.js │ └── index.spec.js ├── keyword-useless-comma │ ├── fixture │ │ ├── useless-comma.gs │ │ └── useless-comma.js │ ├── index.js │ ├── index.spec.js │ └── remove-unnamed-object-property │ │ ├── index.js │ │ └── index.spec.js ├── keyword-useless-semicolon │ ├── fixture │ │ ├── useless-semicolon.gs │ │ └── useless-semicolon.js │ ├── index.js │ └── index.spec.js ├── operator-safe-assignment │ ├── fixture │ │ ├── not-supported.gs │ │ ├── safe-assignment.gs │ │ └── safe-assignment.js │ ├── index.js │ └── index.spec.js ├── operator │ ├── index.js │ └── scopeflags.js ├── parser │ └── index.js ├── printer │ ├── index.js │ ├── index.spec.js │ └── visitors │ │ ├── await-expression.js │ │ ├── await-expression.spec.js │ │ ├── call-expression.js │ │ ├── if-statement.js │ │ ├── try-statement.js │ │ └── try-statement.spec.js ├── string-interpolation │ ├── fixture │ │ ├── number.gs │ │ ├── simple.gs │ │ └── simple.js │ ├── index.js │ └── index.spec.js ├── test │ └── index.js └── types │ ├── if.js │ └── try.js └── rules └── goldstein ├── .gitignore ├── .madrun.js ├── .npmignore ├── .nycrc.json ├── LICENSE ├── README.md ├── eslint.config.js ├── lib ├── convert-t-compile-to-compile │ ├── fixture │ │ ├── convert-t-compile-to-compile-fix.js │ │ ├── convert-t-compile-to-compile.js │ │ └── done.js │ ├── index.js │ └── index.spec.js ├── convert-t-raise-to-raise │ ├── fixture │ │ ├── convert-t-raise-to-raise-fix.js │ │ └── convert-t-raise-to-raise.js │ ├── index.js │ └── index.spec.js ├── index.js └── transform.js ├── package.json └── test ├── fixture ├── convert-t-compile-to-compile-fix.js ├── convert-t-compile-to-compile.js ├── convert-t-raise-to-raise-fix.js └── convert-t-raise-to-raise.js └── goldstein.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: coderaiser 2 | patreon: coderaiser 3 | open_collective: cloudcmd 4 | ko_fi: coderaiser 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | *** 2 | 3 | name: Feature request 4 | about: I want a new syntax construction to be implemented 5 | title: '' 6 | labels: '' 7 | assignees: '' 8 | 9 | *** 10 | 11 | Here is how it will look like: 12 | 13 | ```gc 14 | // here is code in Goldstein 15 | ``` 16 | 17 | And here is how it looks in JavaScript: 18 | 19 | ```js 20 | // here is code in JavaScript 21 | ``` 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | - [ ] commit message named according to [Contributing Guide](https://github.com/coderaiser/cloudcmd/blob/master/CONTRIBUTING.md "Contributting Guide") 7 | - [ ] `npm run codestyle` is OK 8 | - [ ] `npm test` is OK 9 | -------------------------------------------------------------------------------- /.github/workflows/nodejs-pr.yml: -------------------------------------------------------------------------------- 1 | name: Node PR CI 2 | on: 3 | - pull_request 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | node-version: 10 | - 20.x 11 | - 22.x 12 | - 24.x 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: oven-sh/setup-bun@v1 16 | with: 17 | bun-version: latest 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - name: Install 23 | run: bun install --no-save 24 | - name: Lint 25 | run: npm run lint 26 | - name: Install Rust 27 | run: rustup update 28 | - uses: actions/cache@v4 29 | with: 30 | path: | 31 | ~/.cargo/bin/ 32 | ~/.cargo/registry/index/ 33 | ~/.cargo/registry/cache/ 34 | ~/.cargo/git/db/ 35 | target/ 36 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 37 | - name: Typos Install 38 | run: cargo install typos-cli || echo 'already installed' 39 | - name: Typos 40 | run: typos 41 | - name: Coverage 42 | run: npm run coverage 43 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | on: 3 | - push 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | env: 8 | NAME: goldstein 9 | strategy: 10 | matrix: 11 | node-version: 12 | - 20.x 13 | - 22.x 14 | - 24.x 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | repository: ${{ github.event.pull_request.head.repo.full_name }} 19 | ref: ${{ github.event.pull_request.head.ref }} 20 | - uses: oven-sh/setup-bun@v1 21 | with: 22 | bun-version: latest 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - name: Install 28 | run: bun install --no-save 29 | - name: Lint 30 | run: npm run fix:lint 31 | - name: Install Rust 32 | run: rustup update 33 | - uses: actions/cache@v4 34 | with: 35 | path: | 36 | ~/.cargo/bin/ 37 | ~/.cargo/registry/index/ 38 | ~/.cargo/registry/cache/ 39 | ~/.cargo/git/db/ 40 | target/ 41 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 42 | - name: Typos Install 43 | run: cargo install typos-cli || echo 'already installed' 44 | - name: Typos 45 | run: typos --write-changes 46 | - name: Commit fixes 47 | uses: EndBug/add-and-commit@v9 48 | continue-on-error: true 49 | with: 50 | message: "chore: ${{ env.NAME }}: actions: lint ☘️" 51 | - name: Coverage 52 | run: npm run coverage 53 | - name: Coveralls 54 | uses: coverallsapp/github-action@v2 55 | continue-on-error: true 56 | with: 57 | github-token: ${{ secrets.GITHUB_TOKEN }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | yarn-error.log 4 | yarn.lock 5 | .idea 6 | .DS_Store 7 | build 8 | 9 | coverage 10 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: npm install 3 | github: 4 | prebuilds: 5 | addBadge: true 6 | vscode: 7 | extensions: 8 | - vscodevim.vim 9 | -------------------------------------------------------------------------------- /.madrun.js: -------------------------------------------------------------------------------- 1 | import {run} from 'madrun'; 2 | 3 | export default { 4 | 'test': () => `tape 'packages/**/*.spec.js'`, 5 | 'coverage': async () => `c8 ${await run('test')}`, 6 | 'lint': () => `putout .`, 7 | 'fix:lint': () => run('lint', '--fix'), 8 | 'wisdom': () => run(['lint', 'coverage']), 9 | }; 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.spec.* 3 | test 4 | yarn-error.log 5 | dist 6 | fixture 7 | coverage 8 | examples 9 | rules 10 | 11 | *.config.* 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "check-coverage": true, 3 | "all": true, 4 | "exclude": [ 5 | "bin", 6 | "examples", 7 | "rules", 8 | ".*", 9 | "**/*.spec.*", 10 | "test", 11 | "**/fixture", 12 | "**/*.config.*", 13 | "**/test" 14 | ], 15 | "branches": 100, 16 | "lines": 100, 17 | "functions": 100, 18 | "statements": 100 19 | } 20 | -------------------------------------------------------------------------------- /.putout.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "putout/apply-namespace-specifier": "off", 4 | "putout/apply-lowercase-to-node-builders": "on", 5 | "putout/declare": "on", 6 | "github/convert-npm-to-bun": "off" 7 | }, 8 | "match": { 9 | ".filesystem.json": { 10 | "eslint/convert-rc-to-flat": "on" 11 | } 12 | }, 13 | "ignore": [ 14 | "examples/*.js" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude= ["ChangeLog"] 3 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2025.06.01, v6.1.0 2 | 3 | feature: 4 | - a397e22 goldstein: lowercased 5 | 6 | 2025.06.01, v6.0.3 7 | 8 | feature: 9 | - 644fac7 goldstein: @putout/printer v15.0.0 10 | 11 | 2025.05.29, v6.0.2 12 | 13 | feature: 14 | - 7dcb1b1 goldstein: eslint-plugin-putout v27.0.0 15 | - 843620e goldstein: @putout/plugin-try-catch v6.0.0 16 | 17 | 2025.04.09, v6.0.1 18 | 19 | feature: 20 | - 7df9a4e goldstein: putout v40.0.1 21 | - 219f54a goldstein: estree-to-babel v11.0.2 22 | - 8525d2a goldstein: check-dts v0.9.0 23 | - 44818e8 goldstein: @putout/plugin-try-catch v5.0.0 24 | 25 | 2025.03.26, v6.0.0 26 | 27 | feature: 28 | - 14bc734 goldstein: supertape v11.0.4 29 | - 1f56593 goldstein: redlint v4.1.1 30 | - 1dc7799 goldstein: putout v39.1.0 31 | - 3a411e0 goldstein: madrun v11.0.0 32 | - 8625cff goldstein: eslint-plugin-putout v26.1.0 33 | - de100b9 goldstein: @putout/test v13.0.0 34 | - 460b817 goldstein: @putout/printer v14.1.1 35 | - 6b5fd64 goldstein: @putout/eslint-flat v3.0.0 36 | - 4eecbfe goldstein: drop support of node < 20 37 | 38 | 2025.03.14, v5.27.0 39 | 40 | fix: 41 | - ba84d6a goldstein: empty loc 42 | 43 | feature: 44 | - f781982 goldstein: @putout/test v12.1.0 45 | - 3c85787 goldstein: eslint-plugin-putout v25.1.2 46 | 47 | 2025.02.17, v5.26.1 48 | 49 | feature: 50 | - 53401b5 goldstein: @putout/plugin-declare v5.0.0 51 | - 80d3688 goldstein: @putout/printer v13.0.0 52 | 53 | 2025.02.10, v5.26.0 54 | 55 | fix: 56 | - e27d2fb goldstein: hashbang 57 | 58 | feature: 59 | - e60ddb8 goldstein: esbuild v0.25.0 60 | 61 | 2025.01.29, v5.25.1 62 | 63 | feature: 64 | - 07183c4 goldstein: eslint-plugin-putout v24.0.0 65 | - 4e33640 goldstein: putout v38.0.0 66 | 67 | 2025.01.21, v5.25.0 68 | 69 | feature: 70 | - 8d69b27 goldstein: estree-to-babel v10.1.0 71 | 72 | 2025.01.21, v5.24.0 73 | 74 | feature: 75 | - 9cbc2b7 goldstein: preserve parenthesized 76 | 77 | 2025.01.12, v5.23.0 78 | 79 | feature: 80 | - d4a789a goldstein: @putout/printer v12.0.0 81 | 82 | 2024.12.22, v5.22.1 83 | 84 | feature: 85 | - 6e95200 goldstein: keyword-broken-string: goldstein methods 86 | 87 | 2024.12.22, v5.22.0 88 | 89 | feature: 90 | - a7981c9 goldstein: broken string: improve 91 | 92 | 2024.12.19, v5.21.1 93 | 94 | feature: 95 | - d949ff5 goldstein: keyword-export-no-const: no declaration.id 96 | 97 | 2024.12.19, v5.21.0 98 | 99 | feature: 100 | - c0041b4 goldstein: wrong brace 101 | - a774470 goldstein: putout v37.0.0 102 | 103 | 2024.12.12, v5.20.0 104 | 105 | feature: 106 | - 01d6d6f goldstein: keyword-export-no-const: add 107 | 108 | 2024.12.12, v5.19.2 109 | 110 | feature: 111 | - 7a44206 goldstein: @putout/plugin-logical-expressions v7.0.0 112 | 113 | 2024.12.10, v5.19.1 114 | 115 | feature: 116 | - dd22ac9 goldstein: @putout/printer v11.0.0 117 | 118 | 2024.10.27, v5.19.0 119 | 120 | feature: 121 | - 76cb57d goldstein: @putout/printer v10.0.1 122 | - ee20f4e goldstein: estree-to-babel v10.0.0 123 | - ebd230e goldstein: esbuild v0.24.0 124 | 125 | 2024.09.06, v5.18.0 126 | 127 | feature: 128 | - 641db76 goldstein: eslint-plugin-putout v23.1.0 129 | - 84be18f goldstein: @putout/plugin-try-catch v4.0.0 130 | - b2b030b goldstein: add support of safe assignment operator (https://github.com/arthurfiorette/proposal-safe-assignment-operator/issues/41) 131 | 132 | 2024.07.27, v5.17.0 133 | 134 | feature: 135 | - 7bf4e42 goldstein: putout v36.0.3 136 | - 9c03d48 goldstein: @putout/test v11.0.0 137 | - 5c329c3 goldstein: esbuild v0.23.0 138 | 139 | 2024.06.11, v5.16.0 140 | 141 | feature: 142 | - dc62f70 keyword-useless-comma: improve support of identifiers 143 | - b09a906 @putout/plugin-goldstein: c8 v10.0.0 144 | - 7873c60 goldstein: c8 v10.0.0 145 | 146 | 2024.06.07, v5.15.1 147 | 148 | feature: 149 | - 40d2966 goldstein: @putout/printer v9.0.1 150 | 151 | 2024.06.03, v5.15.0 152 | 153 | feature: 154 | - 5d11929 goldstein: keyword-useless-comma: class methods 155 | 156 | 2024.06.03, v5.14.0 157 | 158 | feature: 159 | - 7e81e0e goldstein: keyword-assign-from: add 160 | 161 | 2024.06.03, v5.13.2 162 | 163 | fix: 164 | - 775a6fc goldstein: keyword: broken-string: infinite loop 165 | 166 | feature: 167 | - 4eb6237 goldstein: @putout/test v10.0.0 168 | 169 | 2024.05.31, v5.13.1 170 | 171 | fix: 172 | - 1d705cf goldstein: keyword-useless-comma: rename-unnamed-identifier: StringLiteral 173 | 174 | 2024.05.31, v5.13.0 175 | 176 | feature: 177 | - c16a034 golstein: useless-semicolon 178 | 179 | 2024.05.30, v5.12.0 180 | 181 | feature: 182 | - ade2799 goldstein: useless comma 183 | 184 | 2024.05.28, v5.11.0 185 | 186 | feature: 187 | - c84aaa4 goldstein: add support of jsx/typescript 188 | 189 | 2024.05.27, v5.10.0 190 | 191 | feature: 192 | - 05d0b92 goldstein: @putout/plugin-logical-expressions v6.0.0 193 | - 1554718 goldstein: Import: identifier 194 | 195 | 2024.05.23, v5.9.0 196 | 197 | feature: 198 | - 1a9585b goldstein: missing-initializer: add 199 | 200 | 2024.05.22, v5.8.0 201 | 202 | feature: 203 | - 2bd6e8c test: do not fail when update tests 204 | - f4fb1d6 goldstein: broken strings 205 | - e80d132 goldstein: check-dts v0.8.0 206 | 207 | 2024.05.11, v5.7.2 208 | 209 | fix: 210 | - 3fddd10 goldstein: rm unused 211 | 212 | 2024.05.11, v5.7.1 213 | 214 | feature: 215 | - 9856dbc goldstein: make esbuild optional 216 | 217 | 2024.05.09, v5.7.0 218 | 219 | feature: 220 | - 2f2fecf goldstein: add ability to disable keyword 221 | 222 | 2024.05.09, v5.6.1 223 | 224 | fix: 225 | - ecd46b4 goldstein: override keywords 226 | 227 | 2024.05.08, v5.6.0 228 | 229 | feature: 230 | - 4be8b79 goldstein: keyword-fn: add constant 231 | - f0b7d54 goldstein: keyword-fn: add ability to use "fn" as function name 232 | 233 | 2024.05.08, v5.5.2 234 | 235 | fix: 236 | - 811815f exports 237 | 238 | 2024.05.07, v5.5.1 239 | 240 | fix: 241 | - b229122 goldstein: export 242 | 243 | 2024.05.07, v5.5.0 244 | 245 | feature: 246 | - 44dee08 goldstein: get rid of ability to parse no async code with await 247 | 248 | 2024.05.07, v5.4.0 249 | 250 | feature: 251 | - 0758d6a rm build 252 | 253 | 2024.05.06, v5.3.0 254 | 255 | feature: 256 | - 98f3048 goldstein: add ability to parse no async code with await 257 | - 45c7363 @putout/plugin-goldstein: eslint-plugin-putout v22.7.0 258 | - 3964832 @putout/plugin-goldstein: @putout/test v9.1.0 259 | - aa689c7 @putout/plugin-goldstein: eslint v9.2.0 260 | - 1399854 @putout/plugin-goldstein: c8 v9.1.0 261 | - a728422 goldstein: redlint v3.14.1 262 | - 6f03127 goldstein: eslint v9.2.0 263 | - 23c8eb3 goldstein: @putout/plugin-declare v4.0.0 264 | 265 | 2024.02.20, v5.2.0 266 | 267 | feature: 268 | - 5728421 goldstein: improve support of comments 269 | - 0d997c3 goldstein: printer: visitors: await-expression: maybeVisitor 270 | 271 | 2024.02.19, v5.1.0 272 | 273 | feature: 274 | - c329ff0 goldstein: printer: visitors: try-statememnt: improve support 275 | 276 | 2024.02.19, v5.0.2 277 | 278 | feature: 279 | - 7b1e60e goldstein: supertape v10.1.0 280 | - 448fd5b goldstein: @putout/test v9.0.1 281 | - 9e4433d goldstein: esbuild v0.20.1 282 | - 302b0c4 goldstein: @putout/plugin-declare v3.0.0 283 | 284 | 2024.01.29, v5.0.1 285 | 286 | feature: 287 | - ea5ffdd goldstein: @putout/printer v8.0.1 288 | - 74221ae ifStatement: VariableDeclaration: drop semicolons 289 | - fa700a3 goldstein: c8 v9.1.0 290 | - 28b77bb goldstein: putout v35.0.0 291 | 292 | 2023.12.12, v5.0.0 293 | 294 | feature: 295 | - 3e54d9c goldstein: escover v4.0.1 296 | - 9c35606 goldstein: drop support of node < 18 297 | - fb7a96d goldstein: @putout/plugin-logical-expressions v5.0.0 298 | - 51f1ff6 goldstein: @putout/printer v7.1.0 299 | - 27106cb goldstein: @putout/test v8.0.0 300 | - 04d7d2c goldstein: madrun v10.0.0 301 | - f477bb7 goldstein: eslint-plugin-putout v22.0.0 302 | - c8391d9 goldstein: putout v34.0.7 303 | - 0c81207 goldstein: supertape v9.0.0 304 | - fa384a5 goldstein: estree-to-babel v9.0.0 305 | 306 | 2023.10.28, v4.12.0 307 | 308 | feature: 309 | - 9cd55bd package: @putout/printer v6.0.0 310 | - 3ff2ebe package: eslint-plugin-putout v21.0.1 311 | 312 | 2023.10.19, v4.11.0 313 | 314 | feature: 315 | - 005959d goldstein: convert: if let 316 | 317 | 2023.10.19, v4.10.0 318 | 319 | feature: 320 | - 0b4756a goldstein: add if let 321 | 322 | 2023.10.19, v4.9.1 323 | 324 | fix: 325 | - aff82e3 goldstein: simplify 326 | 327 | 2023.10.19, v4.9.0 328 | 329 | feature: 330 | - 2346464 goldstein: convert: add-array 331 | 332 | 2023.10.19, v4.8.0 333 | 334 | feature: 335 | - 924f182 goldstein: add-array: add 336 | 337 | 2023.10.04, v4.7.0 338 | 339 | feature: 340 | - 136f4a9 goldstein: convert: rm useless import of try-catch 341 | - fcb8679 package: estree-to-babel v8.0.0 342 | 343 | 2023.10.03, v4.6.0 344 | 345 | feature: 346 | - 04ca141 goldstein: export print, convert 347 | 348 | 2023.10.03, v4.5.0 349 | 350 | feature: 351 | - c55a3d1 goldstein: parse: type 352 | - db6bcbe goldstein: types 353 | 354 | 2023.10.03, v4.4.0 355 | 356 | feature: 357 | - 02f16ef goldstein: add convert js to gs 358 | 359 | 2023.10.03, v4.3.0 360 | 361 | feature: 362 | - 77cd780 goldstein: print: if 363 | 364 | 2023.10.03, v4.2.0 365 | 366 | feature: 367 | - e5fb5c9 goldstein: printer: add ability to print goldstein: try 368 | 369 | 2023.09.18, v4.1.2 370 | 371 | feature: 372 | - 99cf10f package: @putout/printer v5.0.0 373 | - 5471f17 package: eslint-plugin-putout v20.0.0 374 | 375 | 2023.09.14, v4.1.1 376 | 377 | feature: 378 | - 003e2ce package: putout v32.0.1 379 | 380 | 2023.09.13, v4.1.0 381 | 382 | feature: 383 | - a7b3eae package: @putout/printer v4.2.0 384 | - 5c23933 package: estree-to-babel v7.0.0 385 | 386 | 2023.08.30, v4.0.2 387 | 388 | feature: 389 | - 6f46703 parser options 390 | 391 | 2023.08.29, v4.0.1 392 | 393 | fix: 394 | - 999445f goldstein: get back sync 395 | 396 | 2023.08.28, v4.0.0 397 | 398 | feature: 399 | - 37f9e82 goldstein: async 400 | - 901a4fa rules: convert-t-raise-to-raise 401 | - cb5d441 rules: add convert-t-raise-to-raise 402 | - 86c5230 rules: add 403 | - 8a8dd67 package: esbuild v0.19.2 404 | - 8501a14 package: @putout/printer v3.6.0 405 | 406 | 2023.08.09, v3.3.2 407 | 408 | fix: 409 | - 456c500 should: typo 410 | 411 | 2023.08.07, v3.3.1 412 | 413 | feature: 414 | - af6f474 package: eslint-plugin-putout v19.0.3 415 | - 2ecbcd5 package: putout v31.0.3 416 | 417 | 2023.07.09, v3.3.0 418 | 419 | feature: 420 | - bbe6942 goldstein: update fixtures 421 | - 34e8be9 package: @putout/printer v2.61.0 422 | - 483a094 package: esbuild v0.18.11 423 | - 2ccfc8b package: escover v3.4.0 424 | - c49f5b6 package: eslint-plugin-putout v18.0.0 425 | - 86e8467 package: estree-to-babel v6.0.0 426 | - eee1ae4 package: c8 v8.0.0 427 | - 92dce62 package: putout v30.2.0 428 | - d7f423b package: eslint-plugin-n v16.0.1 429 | - 430bceb package: nodemon v3.0.1 430 | 431 | 2023.04.19, v3.2.4 432 | 433 | fix: 434 | - dc9866d parser: comment 435 | 436 | 2023.04.19, v3.2.3 437 | 438 | fix: 439 | - bf330c5 goldstein: add support of tokens 440 | 441 | 2023.04.19, v3.2.2 442 | 443 | fix: 444 | - 1ec307f madrun: prepublish -> wisdom 445 | 446 | 2023.04.19, v3.2.1 447 | 448 | fix: 449 | - fd137e5 parser: options 450 | 451 | 2023.04.18, v3.2.0 452 | 453 | feature: 454 | - 76a336d golstein: add support of CommonJS 455 | 456 | 2023.04.18, v3.1.0 457 | 458 | feature: 459 | - 4236346 goldstein: add ability to use arrow in FunctionDeclaration 460 | - 8e2197f goldstein: add ability tot pass keywords and 🐊Putout optionts 461 | 462 | 2023.04.02, v3.0.0 463 | 464 | feature: 465 | - 3eeeab7 goldstein: add ability to simplify logical expressions during compilation 466 | - 7959e57 goldstein: add ability to compile import gs to js 467 | - c61e500 goldstein: improve compiler, disable bundling 468 | - 6cd279e goldstein: enable support of ifStatments with no round braces 469 | - 0ba6f9c major: goldstein: compile: use @putout/pinter instead of recast, parse: provide Babel AST 470 | 471 | 2023.04.01, v2.6.0 472 | 473 | feature: 474 | - ccdb0f1 package: esbuild v0.17.14 475 | - ecf9023 package: eslint-plugin-putout v17.2.1 476 | - 15a95e5 package: typescript v5.0.3 477 | - 84a6049 package: putout v29.1.11 478 | - d4f22c6 package: check-dts v0.7.1 479 | 480 | 2023.01.06, v2.5.1 481 | 482 | feature: 483 | - package: esbuild v0.16.14 484 | - package: putout v28.5.0 485 | 486 | 2022.11.14, v2.5.0 487 | 488 | feature: 489 | - goldsten: export parse 490 | 491 | 2022.10.20, v2.4.2 492 | 493 | feature: 494 | - package: putout v28.0.0 495 | - package: esbuild v0.15.7 496 | - package: @types/acorn v6.0.0 497 | - package: supertape v8.0.1 498 | 499 | 2022.07.20, v2.4.1 500 | 501 | feature: 502 | - package: eslint-plugin-n v15.2.4 503 | - package: eslint-plugin-putout v16.0.0 504 | - package: putout v27.0.1 505 | 506 | 507 | 2022.06.30, v2.4.0 508 | 509 | feature: 510 | - goldstein: freeze: simplify: MemberExpression -> Identifier 511 | - goldstein: add freeze keyword (#6) 512 | 513 | 514 | 2022.06.27, v2.3.0 515 | 516 | feature: 517 | - goldstein: add curry 518 | 519 | 520 | 2022.06.27, v2.2.0 521 | 522 | feature: 523 | - goldstein: add support of throw expressions 524 | 525 | 526 | 2022.06.25, v2.1.0 527 | 528 | feature: 529 | - goldstein: integrate 'should' 530 | - add should keyword (#5) 531 | 532 | 533 | 2022.06.22, v2.0.0 534 | 535 | feature: 536 | - goldstein: keyword: safe -> try 537 | 538 | 539 | 2022.06.22, v1.6.0 540 | 541 | feature: 542 | - goldstein: guard keyword: add ability to omit parens 543 | 544 | 545 | 2022.06.22, v1.5.0 546 | 547 | feature: 548 | - goldstein: keyword if: add ability to omit parens 549 | 550 | 551 | 2022.06.22, v1.4.0 552 | 553 | feature: 554 | - goldstein: keyword safe: add support of VariableDeclaration 555 | 556 | 557 | 2022.06.22, v1.3.0 558 | 559 | feature: 560 | - goldstein: add support of safe await 561 | 562 | 563 | 2022.06.22, v1.2.2 564 | 565 | fix: 566 | - goldstein: cli: add mainFields 567 | 568 | 569 | 2022.06.22, v1.2.1 570 | 571 | fix: 572 | - package: repository url 573 | 574 | 575 | 2022.06.22, v1.2.0 576 | 577 | feature: 578 | - goldstein: add CLI 579 | 580 | 581 | 2022.06.21, v1.1.0 582 | 583 | feature: 584 | - goldstein: add keyword-safe 585 | 586 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) coderaiser 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤫Goldstein [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Coverage Status][CoverageIMGURL]][CoverageURL] 2 | 3 | [NPMURL]: https://npmjs.org/package/goldstein "npm" 4 | [NPMIMGURL]: https://img.shields.io/npm/v/goldstein.svg?style=flat 5 | [BuildStatusURL]: https://github.com/coderaiser/goldstein/actions?query=workflow%3A%22Node+CI%22 "Build Status" 6 | [BuildStatusIMGURL]: https://github.com/coderaiser/goldstein/workflows/Node%20CI/badge.svg 7 | [LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License" 8 | [LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat 9 | [CoverageURL]: https://coveralls.io/github/coderaiser/goldstein?branch=master 10 | [CoverageIMGURL]: https://coveralls.io/repos/coderaiser/goldstein/badge.svg?branch=master&service=github 11 | 12 | ![image](https://user-images.githubusercontent.com/1573141/175353192-9867d3ba-beaf-46d5-adbc-e2eb736bfef1.png) 13 | 14 | > *"You haven't a real appreciation of Newspeak, Winston," he said almost sadly. "Even when you write it you're still thinking in Oldspeak. I've read some of those pieces that you write in The Times occasionally. They're good enough, but they're translations. In your heart you'd prefer to stick to Oldspeak, with all its vagueness and its useless shades of meaning. You don't grasp the beauty of the destruction of words. Do you know that Newspeak is the only language in the world whose vocabulary gets smaller every year?"* 15 | > 16 | > *(c) “1984”, George Orwell* 17 | 18 | JavaScript with no limits 🤫 with built-in JSX and TypeScript. 19 | Language ruled by the users, [create an issue](https://github.com/coderaiser/goldstein/issues/new/choose) with ideas of a new language construction and what is look like in JavaScript, and most likely we implement it :). 20 | 21 | ## Install 22 | 23 | ``` 24 | npm i goldstein esbuild -g 25 | ``` 26 | 27 | ## CLI 28 | 29 | ``` 30 | $ cat > 1.gs 31 | export fn hello() { 32 | return 'world'; 33 | } 34 | 35 | $ gs 1.gs 36 | $ cat 1.js 37 | function hello() { 38 | return "world"; 39 | } 40 | export { 41 | hello, 42 | }; 43 | ``` 44 | 45 | Let's do a bit more! 46 | 47 | ```gs 48 | const a = () => throw 'hello'; 49 | 50 | if a > 2 { 51 | log('hello'); 52 | } 53 | ``` 54 | 55 | Will give us: 56 | 57 | ```js 58 | const a = () => { 59 | throw 'hello'; 60 | }; 61 | 62 | if (a > 2) { 63 | log('hello'); 64 | } 65 | ``` 66 | 67 | ## API 68 | 69 | ### `compile(source)` 70 | 71 | When you need to compile **Goldstein** to **JavaScript** use: 72 | 73 | ```js 74 | import {compile} from 'goldstein'; 75 | 76 | compile(` 77 | fn hello() { 78 | guard text !== "world" else { 79 | return "" 80 | } 81 | 82 | return "Hello " + text 83 | } 84 | `); 85 | 86 | // returns 87 | ` 88 | function hello() { 89 | if (!(text !== 'world')) { 90 | return ''; 91 | } 92 | 93 | return 'Hello ' + text; 94 | } 95 | `; 96 | ``` 97 | 98 | By default, all keywords mentioned in the next section used, but you can limit the list setting with `keywords` option. 99 | You can add any keywords, and even create your own: 100 | 101 | ```js 102 | import {compile, keywords} from 'goldstein'; 103 | 104 | const source = ` 105 | fn hello() { 106 | return id('hello'); 107 | } 108 | `; 109 | 110 | const {keywordFn} = keywords; 111 | 112 | compile(source, { 113 | keywords: { 114 | ...keywords, 115 | keywordFn: null, 116 | keywordId(Parser) { 117 | const {keywordTypes} = Parser.acorn; 118 | 119 | return class extends Parser {}; 120 | }, 121 | }, 122 | rules: { 123 | declare: ['on', { 124 | declarations: { 125 | id: 'const id = (a) => a', 126 | }, 127 | }], 128 | }, 129 | }); 130 | 131 | // returns 132 | ` 133 | const id = (a) => a; 134 | 135 | function hello() { 136 | return id('hello'); 137 | } 138 | `; 139 | ``` 140 | 141 | You can declare variables with [`@putout/operator-declare`](https://github.com/coderaiser/putout/tree/master/packages/operator-declare). 142 | 143 | ### `parse(source, {type, keywords})` 144 | 145 | When you need to get **JavaScript** Babel AST use `parse`: 146 | 147 | ```js 148 | import {parse} from 'goldstein'; 149 | 150 | parse(` 151 | fn hello() { 152 | guard text !== "world" else { 153 | return "" 154 | } 155 | 156 | return "Hello " + text 157 | } 158 | `); 159 | 160 | // returns Babel AST 161 | ``` 162 | 163 | You can parse to **ESTree**: 164 | 165 | ```js 166 | const options = { 167 | type: 'estree', 168 | }; 169 | 170 | parse(` 171 | fn hello() { 172 | guard text !== "world" else { 173 | return "" 174 | } 175 | 176 | return "Hello " + text 177 | `, options); 178 | ``` 179 | 180 | ### `print(ast)` 181 | 182 | You can make any modifications to **Goldstein AST** and then `print` back to **Goldstein**: 183 | 184 | ```js 185 | import {parse, print} from 'goldstein'; 186 | 187 | const ast = parse(`const t = try f('hello')`); 188 | const source = print(ast); 189 | ``` 190 | 191 | ### `convert(source)` 192 | 193 | You can even convert **JavaScript** to **Goldstein** with: 194 | 195 | ```js 196 | import {convert} from 'goldstein'; 197 | 198 | const ast = convert(`const t = tryCatch(f, 'hello')`); 199 | 200 | // returns 201 | `const t = try f('hello')`; 202 | ``` 203 | 204 | ## Keywords 205 | 206 | **Goldstein** is absolutely compatible with JavaScript, and it has extensions. 207 | Here is the list. 208 | 209 | ### `fn` 210 | 211 | You can use `fn` to declare a `function`: 212 | 213 | ```rust 214 | fn hello() { 215 | return 'world'; 216 | } 217 | ``` 218 | 219 | This is the same as: 220 | 221 | ```js 222 | function hello() { 223 | return 'world'; 224 | } 225 | ``` 226 | 227 | ### `append array` 228 | 229 | Append new elements to an array just like in Swift: 230 | 231 | ```js 232 | let a = [1]; 233 | 234 | a += [2, 3]; 235 | ``` 236 | 237 | Is the same as: 238 | 239 | ```js 240 | const a = [1]; 241 | a.push(...[2, 3]); 242 | ``` 243 | 244 | ### `guard` 245 | 246 | Applies not to `IfCondition`: 247 | 248 | ```swift 249 | fn hello() { 250 | guard text !== "world" else { 251 | return "" 252 | } 253 | 254 | return "Hello " + text 255 | } 256 | ``` 257 | 258 | Is the same as: 259 | 260 | ```js 261 | function hello() { 262 | if (text === 'world') { 263 | return ''; 264 | } 265 | 266 | return `Hello ${text}`; 267 | } 268 | ``` 269 | 270 | ### `try` 271 | 272 | `try` can be used as an expression. 273 | 274 | Applies [`tryCatch`](https://github.com/coderaiser/try-catch): 275 | 276 | ```gs 277 | const [error, result] = try hello('world'); 278 | ``` 279 | 280 | Is the same as: 281 | 282 | ```js 283 | import tryCatch from 'try-catch'; 284 | 285 | const [error, result] = tryCatch(hello, 'world'); 286 | ``` 287 | 288 | and 289 | 290 | ```gs 291 | const [error, result] = try await hello('world'); 292 | ``` 293 | 294 | Is the same as: 295 | 296 | ```js 297 | import tryToCatch from 'try-catch'; 298 | 299 | const [error, result] = await tryToCatch(hello, 'world'); 300 | ``` 301 | 302 | ### [`operator-safe-assignment`](https://github.com/arthurfiorette/proposal-safe-assignment-operator) 303 | 304 | You can use `?=` instead of [`try`](#try): 305 | 306 | ```gs 307 | const [error, result] ?= hello('world'); 308 | ``` 309 | 310 | Is the same as: 311 | 312 | ```js 313 | import tryCatch from 'try-catch'; 314 | 315 | const [error, result] = tryCatch(hello, 'world'); 316 | ``` 317 | 318 | and 319 | 320 | ```gs 321 | const [error, result] ?= await hello('world'); 322 | ``` 323 | 324 | Is the same as: 325 | 326 | ```js 327 | import tryToCatch from 'try-catch'; 328 | 329 | const [error, result] = await tryToCatch(hello, 'world'); 330 | ``` 331 | 332 | ### `should` 333 | 334 | `should` can be used as an expression (just like [`try`](https://github.com/coderaiser/goldstein/edit/master/README.md#try)). 335 | This keyword is useful if you want to prevent a function call (also async) to throw an error because you don't need to have any result and the real execution is just optional (so runs if supported). 336 | 337 | ```gs 338 | should hello() 339 | ``` 340 | 341 | Is the same as: 342 | 343 | ```gs 344 | try hello(); 345 | ``` 346 | 347 | > ☝️ *Warning: this feature can be helpful but also dangerous especially if you're debugging your application. In fact, this is made to be used as an optional function call (ex. should load content, but not necessary and knowing this feature is optional), if you call a function in this way while debugging, no error will be printed and the application will continue run as nothing happened.* 348 | 349 | ### `freeze` 350 | 351 | You can use `freeze` instead of `Object.freeze()` like that: 352 | 353 | ```gs 354 | freeze { 355 | 'example': true 356 | } 357 | ``` 358 | 359 | Is the same as: 360 | 361 | ```js 362 | Object.freeze({ 363 | example: true, 364 | }); 365 | ``` 366 | 367 | ### `if` 368 | 369 | You can omit parens. But you must use braces in this case. 370 | 371 | ```swift 372 | if a > 3 { 373 | hello(); 374 | } 375 | ``` 376 | 377 | Also you can use `if let` syntax: 378 | 379 | ```swift 380 | if let x = a?.b { 381 | print(x); 382 | } 383 | ``` 384 | 385 | ### `throw expression` 386 | 387 | You can use [throw as expression](https://github.com/tc39/proposal-throw-expressions), just like that: 388 | 389 | ```js 390 | const a = () => throw 'hello'; 391 | ``` 392 | 393 | ### `Curry` 394 | 395 | Similar to [partial application](https://github.com/tc39/proposal-partial-application): 396 | 397 | ```gs 398 | const sum = (a, b) => a + b; 399 | const inc = sum~(1); 400 | 401 | inc(5); 402 | // returns 403 | 6 404 | ``` 405 | 406 | ### `Import` 407 | 408 | When you import `.gs` files during compile step it will be replaced with `.js`: 409 | 410 | ```gs 411 | // hello.js 412 | export const hello = () => 'world'; 413 | 414 | // index.js1 415 | import hello from './hello.gs'; 416 | ``` 417 | 418 | Will be converted to: 419 | 420 | ```js 421 | // index.js 422 | import hello from './hello.js'; 423 | ``` 424 | 425 | Also, also supported: 426 | 427 | ```gs 428 | import hello from hello; 429 | ``` 430 | 431 | And will be converted to: 432 | 433 | ```js 434 | import hello from 'hello'; 435 | ``` 436 | 437 | ### `FunctionDeclaration` with `Arrow` 438 | 439 | If you mistakenly put `=>` in function declaration: 440 | 441 | ```gs 442 | function hello() => { 443 | } 444 | ``` 445 | 446 | That absolutely fine, it will be converted to: 447 | 448 | ```js 449 | function hello() {} 450 | ``` 451 | 452 | ### Broken String 453 | 454 | When you accidentally broke string, Goldstein will fix it: 455 | 456 | ```diff 457 | -const a = 'hello 458 | +const a = 'hello'; 459 | -const a = ‘hello world’; 460 | +const a = 'hello world'; 461 | ``` 462 | 463 | ### Missing Initializer 464 | 465 | Forget to add assignment (`=`), not problem! 466 | 467 | ```diff 468 | -const {code, places} await samadhi(source); 469 | +const {code, places} = await samadhi(source); 470 | ``` 471 | 472 | ### Useless comma 473 | 474 | Added useless comma (`,`)? no problem! 475 | 476 | ```diff 477 | const a = { 478 | - b,, 479 | + b, 480 | }; 481 | ``` 482 | 483 | ### Useless semicolon 484 | 485 | Added useless semicolon (`;`)? no problem! 486 | 487 | ```diff 488 | const a = { 489 | - b; 490 | + b, 491 | }; 492 | 493 | const a = { 494 | - b(){}, 495 | + b(){} 496 | }; 497 | ``` 498 | 499 | ### Assign from 500 | 501 | ```gs 502 | const a = from 'a'; 503 | ``` 504 | 505 | The same as: 506 | 507 | ```js 508 | const a = require('a'); 509 | ``` 510 | 511 | ### Export without `const` 512 | 513 | ```gs 514 | export x = () => {}; 515 | ``` 516 | 517 | The same as: 518 | 519 | ```js 520 | export const x = () => {}; 521 | ``` 522 | 523 | ### Wrong brace `)` 524 | 525 | ```diff 526 | -import a from 'a'); 527 | +import a from 'a'; 528 | ``` 529 | 530 | ## How to contribute? 531 | 532 | Clone the registry, create a new keyword with a prefix `keyword-`, then create directory `fixture` and put there two files with extensions `.js` and `.gs`. Half way done 🥳! 533 | 534 | Then goes test and implementation in `index.js1` and `index.spec.js` accordingly. Use scripts: 535 | 536 | - `npm test` 537 | - `UPDATE=1 npm test` - update `fixtures`; 538 | - `AST=1 npm test` - log `AST`; 539 | - `npm run coverage`; 540 | - `npm run fix:lint`; 541 | 542 | Update docs and make PR, that's it! 543 | 544 | ## License 545 | 546 | MIT 547 | -------------------------------------------------------------------------------- /bin/gs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { 4 | readFileSync, 5 | writeFileSync, 6 | unlinkSync, 7 | } from 'node:fs'; 8 | import process from 'node:process'; 9 | import esbuild from 'esbuild'; 10 | import {compile} from '../packages/goldstein/index.js'; 11 | 12 | const [arg] = process.argv.slice(2); 13 | 14 | if (!arg) { 15 | console.log('gs '); 16 | process.exit(); 17 | } 18 | 19 | const source = readFileSync(arg, 'utf8'); 20 | const compiled = compile(source); 21 | const compiledName = `~${arg.replace(/\.gs$/, '.js')}`; 22 | 23 | writeFileSync(compiledName, compiled); 24 | 25 | const outfile = compiledName.replace('~', ''); 26 | 27 | esbuild.buildSync({ 28 | entryPoints: [compiledName], 29 | bundle: false, 30 | write: true, 31 | outfile, 32 | mainFields: ['main'], 33 | }); 34 | 35 | unlinkSync(compiledName); 36 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import {safeAlign} from 'eslint-plugin-putout'; 2 | import {matchToFlat} from '@putout/eslint-flat'; 3 | import {defineConfig} from 'eslint/config'; 4 | 5 | export const match = { 6 | '**/bin/**': { 7 | 'n/no-unpublished-import': 'off', 8 | }, 9 | }; 10 | 11 | export default defineConfig([safeAlign, matchToFlat(match)]); 12 | -------------------------------------------------------------------------------- /examples/1.gs: -------------------------------------------------------------------------------- 1 | const a = () => throw 'hello'; 2 | 3 | if a > 2 { 4 | log('hello'); 5 | } 6 | 7 | fn hello() { 8 | console.log('hello'); 9 | } 10 | -------------------------------------------------------------------------------- /examples/1.js: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | throw 'hello'; 3 | }; 4 | 5 | if (a > 2) { 6 | log('hello'); 7 | } 8 | -------------------------------------------------------------------------------- /examples/2.gs: -------------------------------------------------------------------------------- 1 | export fn hello() { 2 | return 'world'; 3 | } 4 | -------------------------------------------------------------------------------- /examples/2.js: -------------------------------------------------------------------------------- 1 | export function hello() { 2 | return 'world'; 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goldstein", 3 | "version": "6.1.0", 4 | "type": "module", 5 | "author": "coderaiser (https://github.com/coderaiser)", 6 | "description": "JavaScript with no limits", 7 | "exports": { 8 | ".": "./packages/goldstein/index.js" 9 | }, 10 | "bin": { 11 | "gs": "bin/gs.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/coderaiser/goldstein.git" 16 | }, 17 | "keywords": [ 18 | "JavaScript", 19 | "parser", 20 | "compiler" 21 | ], 22 | "scripts": { 23 | "test": "madrun test", 24 | "coverage": "madrun coverage", 25 | "lint": "madrun lint", 26 | "fix:lint": "madrun fix:lint", 27 | "wisdom": "madrun wisdom" 28 | }, 29 | "dependencies": { 30 | "@putout/plugin-declare": "^5.0.0", 31 | "@putout/plugin-logical-expressions": "^8.0.0", 32 | "@putout/plugin-try-catch": "^6.0.0", 33 | "@putout/printer": "^15.0.0", 34 | "acorn": "^8.7.1", 35 | "acorn-typescript": "^1.4.13", 36 | "estree-to-babel": "^11.0.2", 37 | "estree-util-attach-comments": "^3.0.0", 38 | "putout": "^40.0.1", 39 | "try-catch": "^3.0.1" 40 | }, 41 | "license": "MIT", 42 | "devDependencies": { 43 | "@cloudcmd/stub": "^4.0.1", 44 | "@putout/eslint-flat": "^3.0.0", 45 | "@putout/plugin-goldstein": "./rules/goldstein", 46 | "@putout/test": "^13.0.0", 47 | "c8": "^10.0.0", 48 | "check-dts": "^0.9.0", 49 | "esbuild": "^0.25.0", 50 | "esbuild-node-builtins": "^0.1.0", 51 | "eslint": "^9.2.0", 52 | "eslint-plugin-putout": "^27.0.0", 53 | "madrun": "^11.0.0", 54 | "mock-require": "^3.0.3", 55 | "montag": "^1.2.1", 56 | "nodemon": "^3.0.1", 57 | "redlint": "^4.1.1", 58 | "runsome": "^1.0.0", 59 | "supertape": "^11.0.4", 60 | "typescript": "^5.0.3" 61 | }, 62 | "engines": { 63 | "node": ">=20" 64 | }, 65 | "publishConfig": { 66 | "access": "public" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/convert/add-array/index.js: -------------------------------------------------------------------------------- 1 | export const report = () => `Use 'add-array'`; 2 | export const replace = () => ({ 3 | '__a.push(...__array)': '__a += __array', 4 | }); 5 | -------------------------------------------------------------------------------- /packages/convert/apply-if-let/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | 3 | const { 4 | variableDeclaration, 5 | variableDeclarator, 6 | } = types; 7 | 8 | export const report = () => `Use 'add-array'`; 9 | export const replace = () => ({ 10 | '{let __a = __b; if (__c) __d}': ({__a, __b}, path) => { 11 | const ifPath = path.get('body.1'); 12 | 13 | ifPath.node.test = variableDeclaration('let', [ 14 | variableDeclarator(__a, __b), 15 | ]); 16 | 17 | return ifPath; 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /packages/convert/apply-try/index.js: -------------------------------------------------------------------------------- 1 | import {createGoldsteinTry} from '../../types/try.js'; 2 | 3 | export const report = () => `Use 'try' instead of 'tryCatch/tryToCatch'`; 4 | export const replace = () => ({ 5 | 'tryCatch(__args)': createTry({ 6 | async: false, 7 | }), 8 | 'await tryToCatch(__args)': createTry({ 9 | async: true, 10 | }), 11 | }); 12 | 13 | const createTry = ({async}) => ({__args}, path) => { 14 | const [callee, ...args] = __args; 15 | 16 | path.node.goldstein = createGoldsteinTry({ 17 | async, 18 | callee, 19 | args, 20 | }); 21 | 22 | return path; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/convert/index.js: -------------------------------------------------------------------------------- 1 | import {estreeToBabel} from 'estree-to-babel'; 2 | import {transform} from 'putout'; 3 | import {print} from '../printer/index.js'; 4 | import * as removeImportTry from './remove-import-try/index.js'; 5 | import * as applyTry from './apply-try/index.js'; 6 | import * as addArray from './add-array/index.js'; 7 | import * as applyIfLet from './apply-if-let/index.js'; 8 | import {fixEmpty, parse} from '../goldstein/index.js'; 9 | 10 | export const convert = (source) => { 11 | const ast = estreeToBabel(parse(source)); 12 | 13 | transform(ast, source, { 14 | plugins: [ 15 | ['add-array', addArray], 16 | ['apply-if-let', applyIfLet], 17 | ['apply-try', applyTry], 18 | ['remove-import-try', removeImportTry], 19 | ], 20 | }); 21 | 22 | return fixEmpty(print(ast)); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/convert/index.spec.js: -------------------------------------------------------------------------------- 1 | import {test} from 'supertape'; 2 | import montag from 'montag'; 3 | import {convert} from './index.js'; 4 | 5 | test('goldstein: convert: tryCatch', (t) => { 6 | const source = `const a = await tryToCatch(f, 'hello')`; 7 | const result = convert(source); 8 | 9 | const expected = montag` 10 | const a = try await f('hello');\n 11 | `; 12 | 13 | t.equal(result, expected); 14 | t.end(); 15 | }); 16 | 17 | test('goldstein: convert: tryCatch: import', (t) => { 18 | const source = ` 19 | import tryCatch from 'try-catch'; 20 | const a = tryCatch(f, 'hello') 21 | `; 22 | 23 | const result = convert(source); 24 | 25 | const expected = montag` 26 | const a = try f('hello');\n 27 | `; 28 | 29 | t.equal(result, expected); 30 | t.end(); 31 | }); 32 | 33 | test('goldstein: convert: add-array', (t) => { 34 | const source = ` 35 | a.push(...[2, 3]); 36 | `; 37 | 38 | const result = convert(source); 39 | 40 | const expected = montag` 41 | a += [2, 3];\n 42 | `; 43 | 44 | t.equal(result, expected); 45 | t.end(); 46 | }); 47 | 48 | test('goldstein: convert: convert-if-let', (t) => { 49 | const source = montag` 50 | { 51 | let a = b?.c; 52 | 53 | if (a) { 54 | log(a); 55 | } 56 | } 57 | `; 58 | 59 | const result = convert(source); 60 | 61 | const expected = montag` 62 | if let a = b?.c { 63 | log(a); 64 | }\n 65 | `; 66 | 67 | t.equal(result, expected); 68 | t.end(); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/convert/remove-import-try/fixture/try-fix.js: -------------------------------------------------------------------------------- 1 | const a = tryCatch(f, 'hello'); 2 | -------------------------------------------------------------------------------- /packages/convert/remove-import-try/fixture/try.js: -------------------------------------------------------------------------------- 1 | import tryCatch from 'try-catch'; 2 | const a = tryCatch(f, 'hello') -------------------------------------------------------------------------------- /packages/convert/remove-import-try/index.js: -------------------------------------------------------------------------------- 1 | export const report = () => `Remove import of 'tryCatch/tryToCatch'`; 2 | export const replace = () => ({ 3 | 'import tryCatch from "try-catch"': '', 4 | 'import tryToCatch from "try-to-catch"': '', 5 | 'const tryCatch = require("try-catch")': '', 6 | 'const tryToCatch = require("try-to-catch")': '', 7 | }); 8 | -------------------------------------------------------------------------------- /packages/convert/remove-import-try/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '@putout/test'; 2 | import * as removeImportTry from './index.js'; 3 | 4 | const test = createTest(import.meta.url, { 5 | printer: 'putout', 6 | plugins: [ 7 | ['remove-import-try', removeImportTry], 8 | ], 9 | }); 10 | 11 | test('plugin-goldstein: remove-import-try: report', (t) => { 12 | t.report('try', `Remove import of 'tryCatch/tryToCatch'`); 13 | t.end(); 14 | }); 15 | 16 | test('plugin-goldstein: remove-import-try: transform', (t) => { 17 | t.transform('try'); 18 | t.end(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/goldstein/fixes/hashbang.js: -------------------------------------------------------------------------------- 1 | export const fixHashbang = (ast) => { 2 | const {comments} = ast; 3 | 4 | if (!comments.length) 5 | return; 6 | 7 | const {value} = comments[0]; 8 | 9 | if (value === '/usr/bin/env node') 10 | ast.interpreter = { 11 | type: 'InterpreterDirective', 12 | value, 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/goldstein/index.js: -------------------------------------------------------------------------------- 1 | import {transform} from 'putout'; 2 | import {print} from '@putout/printer'; 3 | import * as tryCatchPlugin from '@putout/plugin-try-catch'; 4 | import * as declarePlugin from '@putout/plugin-declare'; 5 | import * as logicalExpressionsPlugin from '@putout/plugin-logical-expressions'; 6 | import {parse} from './parser.js'; 7 | 8 | export {convert} from '../convert/index.js'; 9 | export {print} from '../printer/index.js'; 10 | export * from './parser.js'; 11 | import * as removeUnnamedObjectProperty from '../keyword-useless-comma/remove-unnamed-object-property/index.js'; 12 | 13 | export const compile = (source, options = {}) => { 14 | const ast = parse(source, options); 15 | 16 | transform(ast, source, { 17 | rules: { 18 | ...options.rules, 19 | }, 20 | plugins: [ 21 | ['try-catch', tryCatchPlugin], 22 | ['declare', declarePlugin], 23 | ['logical-expressions', logicalExpressionsPlugin], 24 | ['remove-unnamed-identifiers', removeUnnamedObjectProperty], 25 | ], 26 | }); 27 | 28 | return fixEmpty(print(ast)); 29 | }; 30 | 31 | export const fixEmpty = (source) => { 32 | return source.replace(/;(\s+)?;/g, ';'); 33 | }; 34 | -------------------------------------------------------------------------------- /packages/goldstein/index.spec.js: -------------------------------------------------------------------------------- 1 | import {test} from 'supertape'; 2 | import montag from 'montag'; 3 | import tryCatch from 'try-catch'; 4 | import { 5 | compile, 6 | keywords, 7 | parse, 8 | print, 9 | convert, 10 | } from './index.js'; 11 | 12 | test('goldstein: compile', (t) => { 13 | const result = compile(` 14 | fn hello() { 15 | } 16 | `); 17 | 18 | const expected = 'function hello() {}\n'; 19 | 20 | t.equal(result, expected); 21 | t.end(); 22 | }); 23 | 24 | test('goldstein: compile: guard', (t) => { 25 | const result = compile(montag` 26 | fn hello() { 27 | guard (text !== 'world') else { 28 | return '' 29 | } 30 | 31 | return 'Hello ' + text 32 | } 33 | `); 34 | 35 | const expected = montag` 36 | function hello() { 37 | if (text === 'world') { 38 | return ''; 39 | } 40 | 41 | return 'Hello ' + text; 42 | } 43 | 44 | `; 45 | 46 | t.equal(result, expected); 47 | t.end(); 48 | }); 49 | 50 | test('goldstein: compile: try', (t) => { 51 | const result = compile(montag` 52 | try hello(a, b, c); 53 | `); 54 | 55 | const expected = montag` 56 | import tryCatch from 'try-catch'; 57 | 58 | tryCatch(hello, a, b, c); 59 | 60 | `; 61 | 62 | t.equal(result, expected); 63 | t.end(); 64 | }); 65 | 66 | test('goldstein: compile: try await', (t) => { 67 | const result = compile(montag` 68 | const [error] = try await hello(); 69 | `); 70 | 71 | const expected = montag` 72 | import tryToCatch from 'try-to-catch'; 73 | 74 | const [error] = await tryToCatch(hello); 75 | 76 | `; 77 | 78 | t.equal(result, expected); 79 | t.end(); 80 | }); 81 | 82 | test('goldstein: compile: operator: safe-assignment', (t) => { 83 | const result = compile(montag` 84 | const [error, result] ?= await fetch('xx'); 85 | `); 86 | 87 | const expected = montag` 88 | import tryToCatch from 'try-to-catch'; 89 | 90 | const [error, result] = await tryToCatch(fetch, 'xx'); 91 | 92 | `; 93 | 94 | t.equal(result, expected); 95 | t.end(); 96 | }); 97 | 98 | test('goldstein: compile: should', (t) => { 99 | const result = compile(montag` 100 | should hello(a, b, c); 101 | `); 102 | 103 | const expected = montag` 104 | import tryCatch from 'try-catch'; 105 | 106 | tryCatch(hello, a, b, c); 107 | 108 | `; 109 | 110 | t.equal(result, expected); 111 | t.end(); 112 | }); 113 | 114 | test('goldstein: compile: freeze', (t) => { 115 | const result = compile(montag` 116 | freeze { 117 | example: true 118 | } 119 | `); 120 | 121 | const expected = montag` 122 | const {freeze} = Object; 123 | 124 | freeze({ 125 | example: true, 126 | });\n 127 | `; 128 | 129 | t.equal(result, expected); 130 | t.end(); 131 | }); 132 | 133 | test('goldstein: compile: sourceType', (t) => { 134 | const result = compile(montag` 135 | export fn hello() {}; 136 | `); 137 | 138 | const expected = montag` 139 | export function hello() {}; 140 | 141 | `; 142 | 143 | t.equal(result, expected); 144 | t.end(); 145 | }); 146 | 147 | test('goldstein: compile: throw expression', (t) => { 148 | const result = compile(montag` 149 | const a = () => throw 'hello'; 150 | `); 151 | 152 | const expected = montag` 153 | const a = () => { 154 | throw 'hello'; 155 | }; 156 | 157 | `; 158 | 159 | t.equal(result, expected); 160 | t.end(); 161 | }); 162 | 163 | test('goldstein: compile: curry', (t) => { 164 | const result = compile(montag` 165 | sum~(5); 166 | `); 167 | 168 | const expected = montag` 169 | import currify from 'currify'; 170 | 171 | currify(sum, 5); 172 | 173 | `; 174 | 175 | t.equal(result, expected); 176 | t.end(); 177 | }); 178 | 179 | test('goldstein: compile: arrow', (t) => { 180 | const result = compile(montag` 181 | function hello() => { 182 | } 183 | `); 184 | 185 | const expected = montag` 186 | function hello() {}\n 187 | `; 188 | 189 | t.equal(result, expected); 190 | t.end(); 191 | }); 192 | 193 | test('goldstein: compile: options', (t) => { 194 | const source = montag` 195 | fn hello() { 196 | return id('hello'); 197 | } 198 | `; 199 | 200 | const {keywordFn} = keywords; 201 | const result = compile(source, { 202 | keywords: [keywordFn], 203 | rules: { 204 | declare: ['on', { 205 | declarations: { 206 | id: 'const id = (a) => a', 207 | }, 208 | }], 209 | }, 210 | }); 211 | 212 | const expected = montag` 213 | const id = (a) => a; 214 | 215 | function hello() { 216 | return id('hello'); 217 | }\n 218 | `; 219 | 220 | t.equal(result, expected); 221 | t.end(); 222 | }); 223 | 224 | test('goldstein: parse: curry', (t) => { 225 | const result = parse(montag` 226 | sum~(5); 227 | `, { 228 | type: 'estree', 229 | }); 230 | 231 | const {expression} = result.body[0]; 232 | 233 | t.equal(expression.callee.name, 'currify'); 234 | t.end(); 235 | }); 236 | 237 | test('goldstein: parse: if', (t) => { 238 | const result = compile(montag` 239 | if a > 3 { 240 | log('hello'); 241 | } 242 | `); 243 | 244 | const expected = montag` 245 | if (a > 3) { 246 | log('hello'); 247 | }\n 248 | `; 249 | 250 | t.equal(result, expected); 251 | t.end(); 252 | }); 253 | 254 | test('goldstein: parse: append array', (t) => { 255 | const result = compile(montag` 256 | const a = [1]; 257 | a += [2, 3]; 258 | `); 259 | 260 | const expected = montag` 261 | const a = [1]; 262 | a.push(...[2, 3]);\n 263 | `; 264 | 265 | t.equal(result, expected); 266 | t.end(); 267 | }); 268 | 269 | test('goldstein: parse: if let', (t) => { 270 | const result = compile(montag` 271 | if let a = b?.c { 272 | log(a); 273 | } 274 | `); 275 | 276 | const expected = montag` 277 | { 278 | let a = b?.c; 279 | 280 | if (a) { 281 | log(a); 282 | } 283 | }\n 284 | `; 285 | 286 | t.equal(result, expected); 287 | t.end(); 288 | }); 289 | 290 | test('goldstein: parse: import', (t) => { 291 | const result = compile(montag` 292 | import hello from './hello.gs'; 293 | `); 294 | 295 | const expected = montag` 296 | import hello from './hello.js';\n 297 | `; 298 | 299 | t.equal(result, expected); 300 | t.end(); 301 | }); 302 | 303 | test('goldstein: parse: assign from', (t) => { 304 | const result = compile(montag` 305 | const a = from 'x'; 306 | `); 307 | 308 | const expected = montag` 309 | const a = require('x');\n 310 | `; 311 | 312 | t.equal(result, expected); 313 | t.end(); 314 | }); 315 | 316 | test('goldstein: parse: broken string', (t) => { 317 | const result = compile(montag` 318 | const a = 'hello; 319 | const b = 'world'; 320 | `); 321 | 322 | const expected = montag` 323 | const a = 'hello'; 324 | const b = 'world';\n 325 | `; 326 | 327 | t.equal(result, expected); 328 | t.end(); 329 | }); 330 | 331 | test('goldstein: parse: missing initializer', (t) => { 332 | const result = compile(montag` 333 | const {code, places} await samadhi(source); 334 | `); 335 | 336 | const expected = montag` 337 | const {code, places} = await samadhi(source);\n 338 | `; 339 | 340 | t.equal(result, expected); 341 | t.end(); 342 | }); 343 | 344 | test('goldstein: parse: import identifier', (t) => { 345 | const result = compile(montag` 346 | import hello from hello; 347 | `); 348 | 349 | const expected = montag` 350 | import hello from 'hello';\n 351 | `; 352 | 353 | t.equal(result, expected); 354 | t.end(); 355 | }); 356 | 357 | test('goldstein: parse: useless comma', (t) => { 358 | const result = compile(montag` 359 | const a = { 360 | b,, 361 | }; 362 | `); 363 | 364 | const expected = montag` 365 | const a = { 366 | b, 367 | };\n 368 | `; 369 | 370 | t.equal(result, expected); 371 | t.end(); 372 | }); 373 | 374 | test('goldstein: parse: useless comma: class', (t) => { 375 | const result = compile(montag` 376 | const a = class { 377 | b() {}, 378 | }; 379 | `); 380 | 381 | const expected = montag` 382 | const a = class { 383 | b() {} 384 | };\n 385 | `; 386 | 387 | t.equal(result, expected); 388 | t.end(); 389 | }); 390 | 391 | test('goldstein: parse: useless semicolon', (t) => { 392 | const result = compile(montag` 393 | const a = { 394 | b; 395 | }; 396 | `); 397 | 398 | const expected = montag` 399 | const a = { 400 | b, 401 | };\n 402 | `; 403 | 404 | t.equal(result, expected); 405 | t.end(); 406 | }); 407 | 408 | test('goldstein: parse: ts', (t) => { 409 | const result = compile(montag` 410 | const a: string = 'hello'; 411 | `); 412 | 413 | const expected = montag` 414 | const a: string = 'hello';\n 415 | `; 416 | 417 | t.equal(result, expected); 418 | t.end(); 419 | }); 420 | 421 | test('goldstein: parse: jsx', (t) => { 422 | const result = compile(montag` 423 | const a = hello; 424 | `); 425 | 426 | const expected = montag` 427 | const a = ( 428 | hello 429 | );\n 430 | `; 431 | 432 | t.equal(result, expected); 433 | t.end(); 434 | }); 435 | 436 | test('goldstein: parse: comment', (t) => { 437 | const result = compile(montag` 438 | import hello from './hello.js'; 439 | 440 | // abc 441 | const x = 5; 442 | `); 443 | 444 | const expected = montag` 445 | import hello from './hello.js'; 446 | 447 | // abc 448 | const x = 5;\n 449 | `; 450 | 451 | t.equal(result, expected); 452 | t.end(); 453 | }); 454 | 455 | test('goldstein: print', (t) => { 456 | const source = `const a = try f('hello');`; 457 | const ast = parse(source); 458 | const result = print(ast); 459 | 460 | t.equal(result, `${source}\n`); 461 | t.end(); 462 | }); 463 | 464 | test('goldstein: convert', (t) => { 465 | const source = `const a = tryCatch(f, 'hello');`; 466 | const result = convert(source); 467 | 468 | const expected = `const a = try f('hello');\n`; 469 | 470 | t.equal(result, expected); 471 | t.end(); 472 | }); 473 | 474 | test('goldstein: compile: enable couple keywords', (t) => { 475 | const source = montag` 476 | function fn() {} 477 | 478 | fn();\n 479 | `; 480 | 481 | const result = compile(source, { 482 | keywords: {}, 483 | }); 484 | 485 | t.equal(result, source); 486 | t.end(); 487 | }); 488 | 489 | test('goldstein: compile: disable keywords', (t) => { 490 | const source = montag` 491 | function fn() {} 492 | 493 | fn();\n 494 | `; 495 | 496 | const result = compile(source, { 497 | keywords: { 498 | ...keywords, 499 | keywordFn: false, 500 | }, 501 | }); 502 | 503 | t.equal(result, source); 504 | t.end(); 505 | }); 506 | 507 | test('goldstein: compile: new line before if', (t) => { 508 | const source = montag` 509 | const keyPath = path.get('key'); 510 | 511 | if (keyPath.isIdentifier() && !keyPath.node.name) 512 | push(path); 513 | `; 514 | 515 | const result = compile(source, { 516 | keywords: { 517 | ...keywords, 518 | keywordFn: false, 519 | }, 520 | }); 521 | 522 | const expected = montag` 523 | const keyPath = path.get('key'); 524 | 525 | if (keyPath.isIdentifier() && !keyPath.node.name) 526 | push(path);\n 527 | `; 528 | 529 | t.equal(result, expected); 530 | t.end(); 531 | }); 532 | 533 | test('goldstein: compile: parse-maybe-array', (t) => { 534 | const source = montag` 535 | a += [1]; 536 | `; 537 | 538 | const [error] = tryCatch(compile, source, { 539 | keywords: { 540 | keywordAddArray: false, 541 | }, 542 | }); 543 | 544 | t.equal(error.message, `☝️Looks like 'keyword-add-array' is missing.`); 545 | t.end(); 546 | }); 547 | 548 | test('goldstein: compile: missing comma', (t) => { 549 | const source = montag` 550 | const { 551 | isCallExpression 552 | isAwaitExpression 553 | } = types; 554 | 555 | class X extends Parser { 556 | parseExprAtom() { 557 | if (this.type === x) 558 | return parseTryStatement(); 559 | } 560 | }; 561 | `; 562 | 563 | const { 564 | keywordUselessSemicolon, 565 | keywordUselessComma, 566 | } = keywords; 567 | 568 | const result = compile(source, { 569 | keywords: { 570 | keywordUselessComma, 571 | keywordUselessSemicolon, 572 | }, 573 | }); 574 | 575 | const expected = montag` 576 | const { 577 | isCallExpression, 578 | isAwaitExpression, 579 | } = types; 580 | 581 | class X extends Parser { 582 | parseExprAtom() { 583 | if (this.type === x) 584 | return parseTryStatement(); 585 | } 586 | };\n 587 | `; 588 | 589 | t.equal(result, expected); 590 | t.end(); 591 | }); 592 | 593 | test('goldstein: compile: export-no-const', (t) => { 594 | const source = montag` 595 | export x = () => {}; 596 | `; 597 | 598 | const result = compile(source); 599 | const expected = montag` 600 | export const x = () => {};\n 601 | `; 602 | 603 | t.equal(result, expected); 604 | t.end(); 605 | }); 606 | 607 | test('goldstein: wrong brace', (t) => { 608 | const result = compile(montag` 609 | import a from 'a'); 610 | 611 | export const minify = (source, options = {}) => {}; 612 | `); 613 | 614 | const expected = montag` 615 | import a from 'a'; 616 | 617 | export const minify = (source, options = {}) => {}; 618 | 619 | `; 620 | 621 | t.equal(result, expected); 622 | t.end(); 623 | }); 624 | 625 | test('goldstein: punctuation: mobile quote', (t) => { 626 | const result = compile(montag` 627 | const a = ‘hello world’; 628 | `); 629 | 630 | const expected = montag` 631 | const a = 'hello world'; 632 | 633 | `; 634 | 635 | t.equal(result, expected); 636 | t.end(); 637 | }); 638 | 639 | test('goldstein: parse: parenthesized', (t) => { 640 | const result = compile(montag` 641 | const buildEnv = (is17 || is20) && console.log('x'); 642 | `); 643 | 644 | const expected = montag` 645 | const buildEnv = (is17 || is20) && console.log('x'); 646 | 647 | `; 648 | 649 | t.equal(result, expected); 650 | t.end(); 651 | }); 652 | 653 | test('goldstein: parse: optional chaining', (t) => { 654 | const result = compile(montag` 655 | parentPath.parentPath?.isTryStatement(); 656 | `); 657 | 658 | const expected = montag` 659 | parentPath.parentPath?.isTryStatement(); 660 | 661 | `; 662 | 663 | t.equal(result, expected); 664 | t.end(); 665 | }); 666 | 667 | test('goldstein: parse: hashbang', (t) => { 668 | const result = compile(montag` 669 | #!/usr/bin/env node 670 | 671 | const a = 5; 672 | `); 673 | 674 | const expected = montag` 675 | #!/usr/bin/env node 676 | 677 | const a = 5;\n 678 | `; 679 | 680 | t.equal(result, expected); 681 | t.end(); 682 | }); 683 | -------------------------------------------------------------------------------- /packages/goldstein/parser.js: -------------------------------------------------------------------------------- 1 | import {estreeToBabel} from 'estree-to-babel'; 2 | import typescript from 'acorn-typescript'; 3 | import {extendParser} from '../parser/index.js'; 4 | import keywordFn from '../keyword-fn/index.js'; 5 | import keywordGuard from '../keyword-guard/index.js'; 6 | import keywordTry from '../keyword-try/index.js'; 7 | import keywordShould from '../keyword-should/index.js'; 8 | import keywordThrow from '../keyword-throw/index.js'; 9 | import stringInterpolation from '../string-interpolation/index.js'; 10 | import keywordCurry from '../keyword-curry/index.js'; 11 | import keywordFreeze from '../keyword-freeze/index.js'; 12 | import keywordIf from '../keyword-if/index.js'; 13 | import keywordImport from '../keyword-import/index.js'; 14 | import keywordArrow from '../keyword-arrow/index.js'; 15 | import keywordAddArray from '../keyword-add-array/index.js'; 16 | import keywordBrokenString from '../keyword-broken-string/index.js'; 17 | import keywordMissingInitializer from '../keyword-missing-initializer/index.js'; 18 | import keywordUselessComma from '../keyword-useless-comma/index.js'; 19 | import keywordUselessSemicolon from '../keyword-useless-semicolon/index.js'; 20 | import keywordAssignFrom from '../keyword-assign-from/index.js'; 21 | import internalParseMaybeAssign from '../internal-parse-maybe-assign/index.js'; 22 | import operatorSafeAssignment from '../operator-safe-assignment/index.js'; 23 | import keywordExportNoConst from '../keyword-export-no-const/index.js'; 24 | import {fixHashbang} from './fixes/hashbang.js'; 25 | 26 | const {values} = Object; 27 | 28 | const defaultKeywords = { 29 | keywordFn, 30 | keywordGuard, 31 | keywordTry, 32 | keywordShould, 33 | keywordThrow, 34 | keywordCurry, 35 | keywordFreeze, 36 | keywordIf, 37 | keywordImport, 38 | keywordArrow, 39 | keywordAddArray, 40 | keywordBrokenString, 41 | stringInterpolation, 42 | keywordMissingInitializer, 43 | keywordUselessComma, 44 | keywordUselessSemicolon, 45 | keywordAssignFrom, 46 | keywordExportNoConst, 47 | operatorSafeAssignment, 48 | }; 49 | 50 | const internals = [ 51 | internalParseMaybeAssign, 52 | ]; 53 | 54 | export const keywords = defaultKeywords; 55 | 56 | export const parse = (source, options = {}) => { 57 | const keywords = options.keywords || defaultKeywords; 58 | const extensions = values(keywords).filter(Boolean); 59 | 60 | const {parse} = extendParser([ 61 | typescript(), 62 | ...internals, 63 | ...extensions, 64 | ]); 65 | 66 | const ast = parse(source); 67 | 68 | fixHashbang(ast); 69 | 70 | if (options.type === 'estree') 71 | return ast; 72 | 73 | return estreeToBabel(ast); 74 | }; 75 | -------------------------------------------------------------------------------- /packages/internal-parse-maybe-assign/fixture/add-array.gs: -------------------------------------------------------------------------------- 1 | a += [b]; -------------------------------------------------------------------------------- /packages/internal-parse-maybe-assign/fixture/assign-from.gs: -------------------------------------------------------------------------------- 1 | const a = from 'x'; -------------------------------------------------------------------------------- /packages/internal-parse-maybe-assign/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from 'acorn'; 2 | import {types} from 'putout'; 3 | import {DestructuringErrors} from '../operator/index.js'; 4 | 5 | const {isArrayExpression} = types; 6 | 7 | export default function internalParseMaybeAssign(Parser) { 8 | return class extends Parser { 9 | parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { 10 | return parseMaybeAssign.call(this, forInit, refDestructuringErrors, afterLeftParse); 11 | } 12 | 13 | goldsteinParseFrom() { 14 | throw Error(`☝️Looks like 'keyword-assign-from' is missing.`); 15 | } 16 | 17 | goldsteinCreateAddArray() { 18 | throw Error(`☝️Looks like 'keyword-add-array' is missing.`); 19 | } 20 | }; 21 | } 22 | 23 | export function parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { 24 | /* c8 ignore start */ 25 | if (this.isContextual('yield')) { 26 | if (this.inGenerator) 27 | return this.parseYield(forInit); 28 | 29 | // The tokenizer will assume an expression is allowed after 30 | // `yield`, but this isn't that kind of yield 31 | this.exprAllowed = false; /* c8 ignore end */ 32 | } 33 | 34 | let ownDestructuringErrors = false; 35 | let oldParenAssign = -1; 36 | let oldTrailingComma = -1; 37 | let oldDoubleProto = -1; 38 | 39 | if (refDestructuringErrors) { 40 | oldParenAssign = refDestructuringErrors.parenthesizedAssign; 41 | oldTrailingComma = refDestructuringErrors.trailingComma; 42 | oldDoubleProto = refDestructuringErrors.doubleProto; 43 | refDestructuringErrors.parenthesizedAssign = -1; 44 | refDestructuringErrors.trailingComma = -1; 45 | } else { 46 | refDestructuringErrors = new DestructuringErrors(); 47 | ownDestructuringErrors = true; 48 | } 49 | 50 | const startPos = this.start; 51 | const {startLoc} = this; 52 | 53 | if (this.type === tt.parenL || this.type === tt.name) { 54 | this.potentialArrowAt = this.start; 55 | this.potentialArrowInForAwait = forInit === 'await'; 56 | } 57 | 58 | let left = this.parseMaybeConditional(forInit, refDestructuringErrors); 59 | 60 | if (afterLeftParse) 61 | left = afterLeftParse.call(this, left, startPos, startLoc); 62 | 63 | /* c8 ignore start */ 64 | if (this.type.isAssign) { 65 | const node = this.startNodeAt(startPos, startLoc); 66 | 67 | node.operator = this.value; 68 | 69 | if (this.type === tt.eq) 70 | left = this.toAssignable(left, false, refDestructuringErrors); 71 | 72 | if (!ownDestructuringErrors) { 73 | refDestructuringErrors.parenthesizedAssign = -1; 74 | refDestructuringErrors.trailingComma = -1; 75 | refDestructuringErrors.doubleProto = -1; 76 | } 77 | 78 | if (refDestructuringErrors.shorthandAssign >= left.start) 79 | refDestructuringErrors.shorthandAssign = -1; 80 | 81 | // reset because shorthand default was used correctly 82 | if (this.type === tt.eq) 83 | this.checkLValPattern(left); 84 | else 85 | this.checkLValSimple(left); 86 | 87 | node.left = left; 88 | this.next(); 89 | node.right = this.parseMaybeAssign(forInit); 90 | 91 | if (oldDoubleProto > -1) 92 | refDestructuringErrors.doubleProto = oldDoubleProto; 93 | 94 | if (node.operator === '+=' && isArrayExpression(node.right)) 95 | return this.goldsteinCreateAddArray(node); 96 | 97 | return this.finishNode(node, 'AssignmentExpression'); 98 | } 99 | 100 | /* c8 ignore end */ 101 | if (ownDestructuringErrors) 102 | this.checkExpressionErrors(refDestructuringErrors, true); 103 | 104 | if (oldParenAssign > -1) 105 | refDestructuringErrors.parenthesizedAssign = oldParenAssign; 106 | 107 | if (oldTrailingComma > -1) 108 | refDestructuringErrors.trailingComma = oldTrailingComma; 109 | 110 | if (left.name === 'from') 111 | return this.goldsteinParseFrom(left); 112 | 113 | return left; 114 | } 115 | -------------------------------------------------------------------------------- /packages/internal-parse-maybe-assign/index.spec.js: -------------------------------------------------------------------------------- 1 | import ts from 'acorn-typescript'; 2 | import {createTest} from '../test/index.js'; 3 | import internalParseMaybeAssign from '../internal-parse-maybe-assign/index.js'; 4 | 5 | const test = createTest(import.meta.url, ts(), internalParseMaybeAssign); 6 | 7 | test('goldstein: keyword: assign-from: raise: add-array', (t) => { 8 | t.raise('add-array', `☝️Looks like 'keyword-add-array' is missing.`); 9 | t.end(); 10 | }); 11 | 12 | test('goldstein: keyword: assign-from: raise: assign-from', (t) => { 13 | t.raise('assign-from', `☝️Looks like 'keyword-assign-from' is missing.`); 14 | t.end(); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/keyword-add-array/fixture/add-array.gs: -------------------------------------------------------------------------------- 1 | a += [1, 2, 3]; -------------------------------------------------------------------------------- /packages/keyword-add-array/fixture/add-array.js: -------------------------------------------------------------------------------- 1 | a.push(...[1, 2, 3]); 2 | -------------------------------------------------------------------------------- /packages/keyword-add-array/fixture/assign-from.gs: -------------------------------------------------------------------------------- 1 | const a = from 'x'; -------------------------------------------------------------------------------- /packages/keyword-add-array/fixture/assign-from.js: -------------------------------------------------------------------------------- 1 | const a = require('x'); 2 | -------------------------------------------------------------------------------- /packages/keyword-add-array/fixture/const.gs: -------------------------------------------------------------------------------- 1 | const a = 'x'; 2 | const b = from; 3 | const c = from(); 4 | const d = from(1, 2); 5 | const e = from * x; 6 | -------------------------------------------------------------------------------- /packages/keyword-add-array/fixture/yield.gs: -------------------------------------------------------------------------------- 1 | function* fn() { 2 | yield x; 3 | } 4 | -------------------------------------------------------------------------------- /packages/keyword-add-array/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | import {parseMaybeAssign} from '../internal-parse-maybe-assign/index.js'; 3 | 4 | const {assign} = Object; 5 | 6 | const { 7 | identifier, 8 | memberExpression, 9 | spreadElement, 10 | } = types; 11 | 12 | export default function keywordAddArray(Parser) { 13 | return class extends Parser { 14 | parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { 15 | return parseMaybeAssign.call(this, forInit, refDestructuringErrors, afterLeftParse); 16 | } 17 | 18 | goldsteinCreateAddArray(node) { 19 | const {left, right} = node; 20 | 21 | assign(node, { 22 | callee: memberExpression(left, identifier('push')), 23 | arguments: [spreadElement(right)], 24 | }); 25 | 26 | return this.finishNode(node, 'CallExpression'); 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/keyword-add-array/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: keyword: add-array', (t) => { 7 | t.compile('add-array'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: add-array: yield', (t) => { 12 | t.noCompile('yield'); 13 | t.end(); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/keyword-arrow/fixture/arrow.gs: -------------------------------------------------------------------------------- 1 | function hello() => {} 2 | function world() {} 3 | -------------------------------------------------------------------------------- /packages/keyword-arrow/fixture/arrow.js: -------------------------------------------------------------------------------- 1 | function hello() {} 2 | 3 | function world() {} 4 | -------------------------------------------------------------------------------- /packages/keyword-arrow/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from 'acorn'; 2 | 3 | export default function fn(Parser) { 4 | return class extends Parser { 5 | parseBlock(createNewLexicalScope, node, exitStrict) { 6 | if (createNewLexicalScope === void 0) 7 | createNewLexicalScope = true; 8 | 9 | if (node === void 0) 10 | node = this.startNode(); 11 | 12 | node.body = []; 13 | // optionally parse arrow 14 | this.eat(tt.arrow); 15 | this.expect(tt.braceL); 16 | 17 | if (createNewLexicalScope) 18 | this.enterScope(0); 19 | 20 | while (this.type !== tt.braceR) { 21 | const stmt = this.parseStatement(null); 22 | node.body.push(stmt); 23 | } 24 | 25 | /* c8 ignore start */ 26 | if (exitStrict) 27 | this.strict = false; 28 | 29 | /* c8 ignore end */ 30 | this.next(); 31 | 32 | if (createNewLexicalScope) 33 | this.exitScope(); 34 | 35 | return this.finishNode(node, 'BlockStatement'); 36 | } 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /packages/keyword-arrow/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: keyword: arrow', (t) => { 7 | t.compile('arrow'); 8 | t.end(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/keyword-assign-from/fixture/add-array.gs: -------------------------------------------------------------------------------- 1 | a += [b]; -------------------------------------------------------------------------------- /packages/keyword-assign-from/fixture/assign-from.gs: -------------------------------------------------------------------------------- 1 | const a = from 'x'; -------------------------------------------------------------------------------- /packages/keyword-assign-from/fixture/assign-from.js: -------------------------------------------------------------------------------- 1 | const a = require('x'); 2 | -------------------------------------------------------------------------------- /packages/keyword-assign-from/fixture/const.gs: -------------------------------------------------------------------------------- 1 | const a = 'x'; 2 | const b = from; 3 | const c = from(); 4 | const d = from(1, 2); 5 | const e = from * x; 6 | -------------------------------------------------------------------------------- /packages/keyword-assign-from/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from '../operator/index.js'; 2 | import {parseMaybeAssign} from '../internal-parse-maybe-assign/index.js'; 3 | 4 | export default function fn(Parser) { 5 | return class extends Parser { 6 | parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { 7 | return parseMaybeAssign.call(this, forInit, refDestructuringErrors, afterLeftParse); 8 | } 9 | 10 | goldsteinParseFrom(node) { 11 | if (this.type === tt.semi) 12 | return node; 13 | 14 | const arg = this.parseExprAtom(); 15 | 16 | return { 17 | type: 'CallExpression', 18 | callee: { 19 | type: 'Identifier', 20 | name: 'require', 21 | }, 22 | arguments: [arg], 23 | }; 24 | } 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/keyword-assign-from/index.spec.js: -------------------------------------------------------------------------------- 1 | import ts from 'acorn-typescript'; 2 | import {createTest} from '../test/index.js'; 3 | import keywordFn from './index.js'; 4 | import internalParseMaybeAssign from '../internal-parse-maybe-assign/index.js'; 5 | 6 | const test = createTest( 7 | import.meta.url, 8 | ts(), 9 | internalParseMaybeAssign, 10 | keywordFn, 11 | ); 12 | 13 | test('goldstein: keyword: assign-from', (t) => { 14 | t.compile('assign-from'); 15 | t.end(); 16 | }); 17 | 18 | test('goldstein: internal: parse-maybe-assign: const: no compile', (t) => { 19 | t.noCompile('const'); 20 | t.end(); 21 | }); 22 | 23 | test('goldstein: internal: parse-maybe-assign: raise: add-array', (t) => { 24 | t.raise('add-array', `☝️Looks like 'keyword-add-array' is missing.`); 25 | t.end(); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/broken-string.gs: -------------------------------------------------------------------------------- 1 | const a = 'hello; 2 | const b = ''; -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/broken-string.js: -------------------------------------------------------------------------------- 1 | const a = 'hello'; 2 | const b = ''; 3 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/infinite.gs: -------------------------------------------------------------------------------- 1 | const a = 'b -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/infinite.js: -------------------------------------------------------------------------------- 1 | const a = ''; 2 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/mobile-quote-close.gs: -------------------------------------------------------------------------------- 1 | const a = 'hello world’; 2 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/mobile-quote-close.js: -------------------------------------------------------------------------------- 1 | const a = 'hello world'; 2 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/mobile-quote-open.gs: -------------------------------------------------------------------------------- 1 | const a = ‘hello world'; -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/mobile-quote-open.js: -------------------------------------------------------------------------------- 1 | const a = 'hello world'; 2 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/mobile-quote.gs: -------------------------------------------------------------------------------- 1 | const a = ‘hello world’; -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/mobile-quote.js: -------------------------------------------------------------------------------- 1 | const a = 'hello world'; 2 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/no-semi.gs: -------------------------------------------------------------------------------- 1 | const a = 'hello 2 | const b = 'x'; -------------------------------------------------------------------------------- /packages/keyword-broken-string/fixture/no-semi.js: -------------------------------------------------------------------------------- 1 | const a = 'hello'; 2 | const b = 'x'; 3 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from '../operator/index.js'; 2 | 3 | const QUOTE = `'`.charCodeAt(0); 4 | const MOBILE_CLOSE_QUOTE = '’'.charCodeAt(0); 5 | const MOBILE_OPEN_QUOTE = '‘'.charCodeAt(0); 6 | 7 | export default function keywordBrokenString(Parser) { 8 | return class extends Parser { 9 | getTokenFromCode(code) { 10 | if (code === MOBILE_OPEN_QUOTE) 11 | return this.readString(MOBILE_CLOSE_QUOTE); 12 | 13 | return super.getTokenFromCode(code); 14 | } 15 | 16 | parseVarStatement(node, kind, allowMissingInitializer) { 17 | this.next(); 18 | this.parseVar(node, false, kind, allowMissingInitializer); 19 | this.eat(tt.semi); 20 | 21 | return this.finishNode(node, 'VariableDeclaration'); 22 | } 23 | 24 | goldsteinWrongQuoteStart(ch) { 25 | if (!ch) 26 | return true; 27 | 28 | return ch === QUOTE || ch === MOBILE_CLOSE_QUOTE; 29 | } 30 | 31 | goldsteinWrongQuoteEnd() { 32 | if (this.input[this.pos - 1] === ';') 33 | --this.pos; 34 | } 35 | 36 | readString(quote) { 37 | let out = ''; 38 | let chunkStart = ++this.pos; 39 | 40 | for (;;) { 41 | const ch = this.input.charCodeAt(this.pos); 42 | 43 | if (this.goldsteinWrongQuoteStart(ch)) 44 | break; 45 | 46 | /* c8 ignore start */ 47 | if (ch === quote) 48 | break; 49 | 50 | if (ch === 92) { 51 | // '\' 52 | out += this.input.slice(chunkStart, this.pos); 53 | out += this.readEscapedChar(false); 54 | chunkStart = this.pos; 55 | } else if (ch === 0x2028 || ch === 0x2029) { 56 | if (this.options.ecmaVersion < 10) 57 | this.raise(this.start, 'Unterminated string constant'); 58 | 59 | ++this.pos; 60 | 61 | if (this.options.locations) { 62 | this.curLine++; 63 | this.lineStart = this.pos; 64 | } 65 | /* c8 ignore end */ 66 | } else { 67 | if (isNewLine(ch)) { 68 | this.goldsteinWrongQuoteEnd(); 69 | break; 70 | } 71 | 72 | ++this.pos; 73 | } 74 | } 75 | 76 | out += this.input.slice(chunkStart, this.pos++); 77 | 78 | return this.finishToken(tt.string, out); 79 | } 80 | }; 81 | } 82 | 83 | function isNewLine(code) { 84 | return code === 10 85 | || code === 13 86 | || code === 0x2028 87 | || code === 0x2029; 88 | } 89 | -------------------------------------------------------------------------------- /packages/keyword-broken-string/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordBrokenString from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordBrokenString); 5 | 6 | test('goldstein: broken-string', (t) => { 7 | t.compile('broken-string'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: broken-string: no-semi', (t) => { 12 | t.compile('no-semi'); 13 | t.end(); 14 | }); 15 | 16 | test('goldstein: broken-string: infinite', (t) => { 17 | t.compile('infinite'); 18 | t.end(); 19 | }); 20 | 21 | test('goldstein: punctuation: mobile-quote', (t) => { 22 | t.compile('mobile-quote'); 23 | t.end(); 24 | }); 25 | 26 | test('goldstein: punctuation: mobile-quote-open', (t) => { 27 | t.compile('mobile-quote-open'); 28 | t.end(); 29 | }); 30 | 31 | test('goldstein: punctuation: mobile-quote-close', (t) => { 32 | t.compile('mobile-quote-close'); 33 | t.end(); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/keyword-curry/fixture/curry.gs: -------------------------------------------------------------------------------- 1 | const inc = sum~(1, 2); -------------------------------------------------------------------------------- /packages/keyword-curry/fixture/curry.js: -------------------------------------------------------------------------------- 1 | const inc = currify(sum, (1, 2)); 2 | -------------------------------------------------------------------------------- /packages/keyword-curry/fixture/raise.gs: -------------------------------------------------------------------------------- 1 | const inc = sum~1; -------------------------------------------------------------------------------- /packages/keyword-curry/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | import {tokTypes as tt} from '../operator/index.js'; 3 | 4 | const {identifier} = types; 5 | 6 | export default function keywordCurry(Parser) { 7 | return class extends Parser { 8 | parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit) { 9 | const isTRA = this.eat(tt.prefix); 10 | 11 | if (isTRA) 12 | return this.parseCurry(base, startPos, startLoc); 13 | 14 | return super.parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit); 15 | } 16 | 17 | parseCurry(base, startPos, startLoc) { 18 | const node = this.startNodeAt(startPos, startLoc); 19 | const isParenL = this.eat(tt.parenL); 20 | 21 | if (!isParenL) 22 | this.raise(this.start, `After '~' should always go '(' when you use curry`); 23 | 24 | node.callee = identifier('currify'); 25 | node.arguments = [base, this.parseExpression()]; 26 | 27 | this.expect(tt.parenR); 28 | 29 | return this.finishNode(node, 'CallExpression'); 30 | } 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /packages/keyword-curry/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordCurry from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordCurry); 5 | 6 | test('goldstein: curry', (t) => { 7 | t.compile('curry'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: curry: raise', (t) => { 12 | t.raise('raise', `After '~' should always go '(' when you use curry (1:16)`); 13 | t.end(); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/keyword-export-no-const/fixture/export-no-const.gs: -------------------------------------------------------------------------------- 1 | export x = () => {}; -------------------------------------------------------------------------------- /packages/keyword-export-no-const/fixture/export-no-const.js: -------------------------------------------------------------------------------- 1 | export const x = () => {}; 2 | -------------------------------------------------------------------------------- /packages/keyword-export-no-const/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | import {tokTypes as tt} from '../operator/index.js'; 3 | 4 | const { 5 | variableDeclarator, 6 | variableDeclaration, 7 | } = types; 8 | 9 | export default function keywordExportNoConst(Parser) { 10 | return class extends Parser { 11 | shouldParseExportStatement() { 12 | if (!this.type.keyword) 13 | return true; 14 | 15 | return super.shouldParseExportStatement(); 16 | } 17 | 18 | parseExport(node, exports) { 19 | this.next(); 20 | 21 | /* c8 ignore start */ 22 | // export * from '...' 23 | if (this.eat(tt.star)) 24 | return this.parseExportAllDeclaration(node, exports); 25 | 26 | /* c8 ignore end */ 27 | /* c8 ignore start */ 28 | if (this.eat(tt._default)) { 29 | // export default ... 30 | this.checkExport(exports, 'default', this.lastTokStart); 31 | node.declaration = this.parseExportDefaultDeclaration(); 32 | 33 | return this.finishNode(node, 'ExportDefaultDeclaration'); 34 | } 35 | 36 | /* c8 ignore end */ 37 | // export var|const|let|function|class ... 38 | if (this.shouldParseExportStatement()) { 39 | node.declaration = this.parseExportDeclaration(node); 40 | 41 | if (node.declaration.type === 'VariableDeclaration') 42 | this.checkVariableExport(exports, node.declaration.declarations); 43 | 44 | if (node.declaration.type === 'ExpressionStatement') 45 | node.declaration = variableDeclaration('const', [ 46 | variableDeclarator(node.declaration.expression.left, node.declaration.expression.right), 47 | ]); 48 | else if (node.declaration.id) 49 | this.checkExport(exports, node.declaration.id, node.declaration.id.start); 50 | 51 | node.specifiers = []; 52 | node.source = null; 53 | } else { 54 | // export { x, y as z } [from '...'] 55 | node.declaration = null; 56 | node.specifiers = this.parseExportSpecifiers(exports); 57 | 58 | if (this.eatContextual('from')) { 59 | if (this.type !== tt.string) 60 | this.unexpected(); 61 | 62 | node.source = this.parseExprAtom(); 63 | 64 | if (this.options.ecmaVersion >= 16) 65 | node.attributes = this.parseWithClause(); 66 | } else { 67 | for (let i = 0, list = node.specifiers; i < list.length; ++i) { 68 | // check for keywords used as local names 69 | const spec = list[i]; 70 | 71 | this.checkUnreserved(spec.local); 72 | // check if export is defined 73 | this.checkLocalExport(spec.local); 74 | 75 | if (spec.local.type === 'Literal') 76 | this.raise(spec.local.start, 'A string literal cannot be used as an exported binding without `from`.'); 77 | } 78 | 79 | node.source = null; 80 | } 81 | 82 | this.semicolon(); 83 | } 84 | 85 | return this.finishNode(node, 'ExportNamedDeclaration'); 86 | } 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /packages/keyword-export-no-const/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordExportNoConst from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordExportNoConst); 5 | 6 | test('goldstein: keyword: export-no-const', (t) => { 7 | t.compile('export-no-const'); 8 | t.end(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/keyword-fn/fixture/export.gs: -------------------------------------------------------------------------------- 1 | export fn hello() {} -------------------------------------------------------------------------------- /packages/keyword-fn/fixture/export.js: -------------------------------------------------------------------------------- 1 | export function hello() {} 2 | -------------------------------------------------------------------------------- /packages/keyword-fn/fixture/fn-function.gs: -------------------------------------------------------------------------------- 1 | function fn() {} 2 | -------------------------------------------------------------------------------- /packages/keyword-fn/fixture/fn.gs: -------------------------------------------------------------------------------- 1 | fn hello() { 2 | } 3 | -------------------------------------------------------------------------------- /packages/keyword-fn/fixture/fn.js: -------------------------------------------------------------------------------- 1 | function hello() {} 2 | -------------------------------------------------------------------------------- /packages/keyword-fn/fixture/other-export.gs: -------------------------------------------------------------------------------- 1 | export function hello() {} -------------------------------------------------------------------------------- /packages/keyword-fn/fixture/other-export.js: -------------------------------------------------------------------------------- 1 | export function hello() {} 2 | -------------------------------------------------------------------------------- /packages/keyword-fn/index.js: -------------------------------------------------------------------------------- 1 | import {addKeyword, TokenType} from '../operator/index.js'; 2 | 3 | const KEYWORD_FN = 'fn'; 4 | 5 | export default function keywordFn(Parser) { 6 | const {keywordTypes} = Parser.acorn; 7 | 8 | keywordTypes.fn = new TokenType(KEYWORD_FN, { 9 | keyword: KEYWORD_FN, 10 | }); 11 | 12 | return class extends Parser { 13 | parse() { 14 | this.keywords = addKeyword(KEYWORD_FN, this.keywords); 15 | 16 | return super.parse(); 17 | } 18 | 19 | parseStatement(context, topLevel, exports) { 20 | if (this.type === keywordTypes.fn) 21 | this.type = keywordTypes.function; 22 | 23 | return super.parseStatement(context, topLevel, exports); 24 | } 25 | 26 | shouldParseExportStatement() { 27 | if (this.type === keywordTypes.fn) 28 | return true; 29 | 30 | return super.shouldParseExportStatement(); 31 | } 32 | 33 | checkUnreserved(ref) { 34 | const {name} = ref; 35 | 36 | if (name === KEYWORD_FN) 37 | return; 38 | 39 | return super.checkUnreserved(ref); 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /packages/keyword-fn/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: keyword: fn', (t) => { 7 | t.compile('fn'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: fn-function', (t) => { 12 | t.noCompile('fn-function'); 13 | t.end(); 14 | }); 15 | 16 | test('goldstein: keyword: fn: export', (t) => { 17 | t.compile('export'); 18 | t.end(); 19 | }); 20 | 21 | test('goldstein: keyword: fn: other-export', (t) => { 22 | t.compile('other-export'); 23 | t.end(); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/keyword-freeze/fixture/array-freeze.gs: -------------------------------------------------------------------------------- 1 | freeze [ 2 | { 3 | "test": true 4 | }, 5 | { 6 | "example": "Hello World!" 7 | } 8 | ] -------------------------------------------------------------------------------- /packages/keyword-freeze/fixture/array-freeze.js: -------------------------------------------------------------------------------- 1 | freeze([{ 2 | 'test': true, 3 | }, { 4 | 'example': 'Hello World!', 5 | }]); 6 | -------------------------------------------------------------------------------- /packages/keyword-freeze/fixture/freeze.gs: -------------------------------------------------------------------------------- 1 | freeze { 2 | "test": true 3 | } -------------------------------------------------------------------------------- /packages/keyword-freeze/fixture/freeze.js: -------------------------------------------------------------------------------- 1 | freeze({ 2 | 'test': true, 3 | }); 4 | -------------------------------------------------------------------------------- /packages/keyword-freeze/fixture/not-supported.gs: -------------------------------------------------------------------------------- 1 | freeze true -------------------------------------------------------------------------------- /packages/keyword-freeze/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | import {addKeyword, TokenType} from '../operator/index.js'; 3 | 4 | const { 5 | isObjectExpression, 6 | isArrayExpression, 7 | } = types; 8 | 9 | export default function keywordFreeze(Parser) { 10 | const {keywordTypes} = Parser.acorn; 11 | 12 | keywordTypes.freeze = new TokenType('freeze', { 13 | keyword: 'freeze', 14 | }); 15 | 16 | return class extends Parser { 17 | parse() { 18 | this.keywords = addKeyword('freeze', this.keywords); 19 | return super.parse(); 20 | } 21 | 22 | parseStatement(context, topLevel, exports) { 23 | if (this.type === keywordTypes.freeze) 24 | return this.parseFreeze(); 25 | 26 | return super.parseStatement(context, topLevel, exports); 27 | } 28 | 29 | parseFreeze() { 30 | this.next(); 31 | 32 | const node = super.startNode(); 33 | const expression = this.parseExpression(); 34 | 35 | if (isObjectExpression(expression) || isArrayExpression(expression)) 36 | node.expression = { 37 | type: 'ExpressionStatement', 38 | expression: { 39 | type: 'CallExpression', 40 | callee: { 41 | type: 'Identifier', 42 | name: 'freeze', 43 | }, 44 | arguments: [expression], 45 | }, 46 | }; 47 | else 48 | this.raise(this.start, `After 'freeze' only objects and arrays can come`); 49 | 50 | return super.finishNode(node, 'ExpressionStatement'); 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /packages/keyword-freeze/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: keyword: freeze', (t) => { 7 | t.compile('freeze'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: freeze (using array)', (t) => { 12 | t.compile('array-freeze'); 13 | t.end(); 14 | }); 15 | 16 | test('goldstein: keyword: freeze (invalid)', (t) => { 17 | t.raise('not-supported', `After 'freeze' only objects and arrays can come (1:11)`); 18 | t.end(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/keyword-guard/fixture/diff-parens.gs: -------------------------------------------------------------------------------- 1 | function hello() { 2 | guard text !== "world") else { 3 | return "" 4 | } 5 | 6 | return "Hello " + text 7 | } -------------------------------------------------------------------------------- /packages/keyword-guard/fixture/guard.gs: -------------------------------------------------------------------------------- 1 | function hello() { 2 | guard (text !== "world") else { 3 | return "" 4 | } 5 | 6 | return "Hello " + text 7 | } -------------------------------------------------------------------------------- /packages/keyword-guard/fixture/guard.js: -------------------------------------------------------------------------------- 1 | function hello() { 2 | if (!text !== 'world') { 3 | return ''; 4 | } 5 | 6 | return 'Hello ' + text; 7 | } 8 | -------------------------------------------------------------------------------- /packages/keyword-guard/fixture/no-parens.gs: -------------------------------------------------------------------------------- 1 | function hello() { 2 | guard text !== "world" else { 3 | return "" 4 | } 5 | 6 | return "Hello " + text 7 | } -------------------------------------------------------------------------------- /packages/keyword-guard/fixture/no-parens.js: -------------------------------------------------------------------------------- 1 | function hello() { 2 | if (!text !== 'world') { 3 | return ''; 4 | } 5 | 6 | return 'Hello ' + text; 7 | } 8 | -------------------------------------------------------------------------------- /packages/keyword-guard/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | addKeyword, 3 | TokenType, 4 | tokTypes, 5 | } from '../operator/index.js'; 6 | 7 | export default function newSpeak(Parser) { 8 | const {keywordTypes} = Parser.acorn; 9 | 10 | keywordTypes.guard = new TokenType('guard', { 11 | keyword: 'guard', 12 | }); 13 | 14 | return class extends Parser { 15 | parse() { 16 | this.keywords = addKeyword('guard', this.keywords); 17 | return super.parse(); 18 | } 19 | 20 | parseStatement(context, topLevel, exports) { 21 | if (this.type === keywordTypes.guard) 22 | return this.parseGuard(); 23 | 24 | return super.parseStatement(context, topLevel, exports); 25 | } 26 | 27 | parseGuard() { 28 | super.next(); 29 | 30 | const { 31 | parenL, 32 | parenR, 33 | _else, 34 | } = tokTypes; 35 | 36 | const node = super.startNode(); 37 | const isParenL = super.eat(parenL); 38 | 39 | node.test = { 40 | type: 'UnaryExpression', 41 | operator: '!', 42 | prefix: true, 43 | argument: super.parseExpression(), 44 | }; 45 | 46 | const isParenR = super.eat(parenR); 47 | 48 | if (isParenL !== isParenR) 49 | this.raise(this.start, `Use both parens ('(', ')') or none`); 50 | 51 | super.expect(_else); 52 | 53 | node.consequent = this.parseStatement(); 54 | node.alternate = null; 55 | 56 | return super.finishNode(node, 'IfStatement'); 57 | } 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /packages/keyword-guard/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: keyword: guard', (t) => { 7 | t.compile('guard'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: guard: no-parens', (t) => { 12 | t.compile('no-parens'); 13 | t.end(); 14 | }); 15 | 16 | test('goldstein: keyword: guard: diff-parens', (t) => { 17 | t.raise('diff-parens', `Use both parens ('(', ')') or none (2:28)`); 18 | t.end(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/keyword-if/fixture/diff-parens.gs: -------------------------------------------------------------------------------- 1 | if (a > 3 2 | alert(); 3 | -------------------------------------------------------------------------------- /packages/keyword-if/fixture/if-let.gs: -------------------------------------------------------------------------------- 1 | let a = 5; 2 | 3 | if let x = john.info?.name { 4 | print(x); 5 | } -------------------------------------------------------------------------------- /packages/keyword-if/fixture/if-let.js: -------------------------------------------------------------------------------- 1 | let a = 5; 2 | { 3 | let x = john.info?.name; 4 | 5 | if (x) { 6 | print(x); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/keyword-if/fixture/if.gs: -------------------------------------------------------------------------------- 1 | if a > 3 { 2 | } 3 | 4 | if (a > 3) 5 | hello(); 6 | else 7 | world(); -------------------------------------------------------------------------------- /packages/keyword-if/fixture/if.js: -------------------------------------------------------------------------------- 1 | if (a > 3) {} 2 | 3 | if (a > 3) 4 | hello(); 5 | else 6 | world(); 7 | -------------------------------------------------------------------------------- /packages/keyword-if/fixture/no-brace.gs: -------------------------------------------------------------------------------- 1 | if a > 3 2 | alert(); 3 | -------------------------------------------------------------------------------- /packages/keyword-if/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from '../operator/index.js'; 2 | import {setGoldsteinIf} from '../types/if.js'; 3 | 4 | export default function fn(Parser) { 5 | return class extends Parser { 6 | parseIfStatement() { 7 | this.next(); 8 | const isParenL = this.eat(tt.parenL); 9 | 10 | if (this.isContextual('let')) 11 | return createIfLet.call(this, { 12 | isParenL, 13 | }); 14 | 15 | const test = this.parseExpression(); 16 | 17 | return createIf.call(this, { 18 | test, 19 | isParenL, 20 | }); 21 | } 22 | }; 23 | } 24 | 25 | function check({isParenL, isParenR}) { 26 | if (!isParenL && !isParenR && this.type !== tt.braceL) 27 | this.raise(this.start, `Use braces ('{', '}') when omit parens ('(', ')')`); 28 | 29 | if (isParenL !== isParenR) 30 | this.raise(this.start, `Use both parens ('(', ')') or none`); 31 | } 32 | 33 | function createIfLet({isParenL}) { 34 | this.next(); 35 | this.eat(tt.assign); 36 | 37 | const assignmentExpression = this.parseExpression(); 38 | const isParenR = this.eat(tt.parenR); 39 | 40 | check.call(this, { 41 | isParenL, 42 | isParenR, 43 | }); 44 | 45 | const ifNode = createIf.call(this, { 46 | test: assignmentExpression.left, 47 | isParenL, 48 | }); 49 | 50 | const node = { 51 | loc: {}, 52 | range: [], 53 | type: 'BlockStatement', 54 | body: [{ 55 | type: 'VariableDeclaration', 56 | kind: 'let', 57 | declarations: [{ 58 | type: 'VariableDeclarator', 59 | id: assignmentExpression.left, 60 | init: assignmentExpression.right, 61 | }], 62 | }, 63 | ifNode], 64 | }; 65 | 66 | return this.finishNode(node, 'BlockStatement'); 67 | } 68 | 69 | function createIf({test, isParenL}) { 70 | const node = { 71 | test, 72 | }; 73 | 74 | const isParenR = this.eat(tt.parenR); 75 | 76 | check.call(this, { 77 | isParenL, 78 | isParenR, 79 | }); 80 | 81 | node.consequent = this.parseStatement('if'); 82 | node.alternate = this.eat(tt._else) ? this.parseStatement('if') : null; 83 | node.range = []; 84 | node.type = 'IfStatement'; 85 | 86 | setGoldsteinIf(node); 87 | 88 | return this.finishNode(node, 'IfStatement'); 89 | } 90 | -------------------------------------------------------------------------------- /packages/keyword-if/index.spec.js: -------------------------------------------------------------------------------- 1 | import ts from 'acorn-typescript'; 2 | import {createTest} from '../test/index.js'; 3 | import keywordFn from './index.js'; 4 | 5 | const test = createTest(import.meta.url, ts(), keywordFn); 6 | 7 | test('goldstein: keyword: if', (t) => { 8 | t.compile('if'); 9 | t.end(); 10 | }); 11 | 12 | test('goldstein: keyword: if: let', (t) => { 13 | t.compile('if-let'); 14 | t.end(); 15 | }); 16 | 17 | test('goldstein: keyword: if: no brace', (t) => { 18 | t.raise('no-brace', `Use braces ('{', '}') when omit parens ('(', ')') (2:4)`); 19 | t.end(); 20 | }); 21 | 22 | test('goldstein: keyword: if: diff-parens', (t) => { 23 | t.raise('diff-parens', `Use both parens ('(', ')') or none (2:4)`); 24 | t.end(); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/keyword-import/fixture/import-identifier.gs: -------------------------------------------------------------------------------- 1 | import f from hello; -------------------------------------------------------------------------------- /packages/keyword-import/fixture/import-identifier.js: -------------------------------------------------------------------------------- 1 | import f from 'hello'; 2 | -------------------------------------------------------------------------------- /packages/keyword-import/fixture/import.gs: -------------------------------------------------------------------------------- 1 | import f from './hello.gs'; -------------------------------------------------------------------------------- /packages/keyword-import/fixture/import.js: -------------------------------------------------------------------------------- 1 | import f from './hello.js'; 2 | -------------------------------------------------------------------------------- /packages/keyword-import/fixture/wrong-brace.gs: -------------------------------------------------------------------------------- 1 | import f from './hello.js'); -------------------------------------------------------------------------------- /packages/keyword-import/fixture/wrong-brace.js: -------------------------------------------------------------------------------- 1 | import f from './hello.js'; 2 | -------------------------------------------------------------------------------- /packages/keyword-import/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes} from '../operator/index.js'; 2 | 3 | const empty = []; 4 | 5 | export default function keywordImport(Parser) { 6 | return class extends Parser { 7 | parseImport(node) { 8 | this.next(); 9 | // import '...' 10 | 11 | /* c8 ignore start */ 12 | if (this.type === tokTypes.string) { 13 | node.specifiers = empty; 14 | node.source = this.parseExprAtom(); /* c8 ignore end */ 15 | } else { 16 | node.specifiers = this.parseImportSpecifiers(); 17 | this.expectContextual('from'); 18 | 19 | if (this.type === tokTypes.string) { 20 | node.source = this.parseLiteral(this.value); 21 | } else if (this.type === tokTypes.name) { 22 | const {value} = this; 23 | 24 | node.source = this.parseLiteral(this.value); 25 | node.source.raw = `'${value}'`; 26 | } else { 27 | this.unexpected(); 28 | } 29 | } 30 | 31 | const {raw, value} = node.source; 32 | 33 | if (value.endsWith('.gs')) { 34 | node.source.raw = raw.replace('.gs', '.js'); 35 | node.source.value = value.replace(/\.gs$/, '.js'); 36 | } 37 | 38 | super.eat(tokTypes.parenR); 39 | this.semicolon(); 40 | 41 | return this.finishNode(node, 'ImportDeclaration'); 42 | } 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /packages/keyword-import/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordImport from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordImport); 5 | 6 | test('goldstein: keyword: import', (t) => { 7 | t.compile('import'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: import: identifier', (t) => { 12 | t.compile('import-identifier'); 13 | t.end(); 14 | }); 15 | 16 | test('goldstein: keyword: import: wrong-brace', (t) => { 17 | t.compile('wrong-brace'); 18 | t.end(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/keyword-missing-initializer/fixture/missing-initializer.gs: -------------------------------------------------------------------------------- 1 | const {code, places} await samadhi(source); -------------------------------------------------------------------------------- /packages/keyword-missing-initializer/fixture/missing-initializer.js: -------------------------------------------------------------------------------- 1 | const {code, places} = await samadhi(source); 2 | -------------------------------------------------------------------------------- /packages/keyword-missing-initializer/fixture/no-safe-assignment.gs: -------------------------------------------------------------------------------- 1 | const [error, response] ?= await fetch("https://arthur.place"); 2 | const [error1, response1] ?= fn("https://arthur.place"); 3 | -------------------------------------------------------------------------------- /packages/keyword-missing-initializer/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from '../operator/index.js'; 2 | 3 | export default function keywordMissingInitializer(Parser) { 4 | return class extends Parser { 5 | parseVar(node, isFor, kind, allowMissingInitializer) { 6 | node.declarations = []; 7 | node.kind = kind; 8 | for (;;) { 9 | const decl = this.startNode(); 10 | this.parseVarId(decl, kind); 11 | 12 | /* c8 ignore start */ 13 | if (this.eat(tt.question) && this.eat(tt.eq)) { 14 | if (!this.parseSafeAssignment) 15 | this.raise(this.lastTokEnd, `Enable 'operator-safe-assignment' to have ability to use '?='`); 16 | 17 | decl.init = this.parseSafeAssignment(); /* c8 ignore start */ 18 | } else if (this.eat(tt.eq)) { 19 | decl.init = this.parseMaybeAssign(isFor); 20 | } else if (!allowMissingInitializer && kind === 'const' && this.type !== tt._in && !(this.options.ecmaVersion >= 6 && this.isContextual('of'))) { 21 | decl.init = MissingInitializer.missInitializer.call(this, isFor); 22 | } else if (!allowMissingInitializer && decl.id.type !== 'Identifier' && !(isFor && (this.type === tt._in || this.isContextual('of')))) { 23 | this.raise(this.lastTokEnd, 'Complex binding patterns require an initialization value'); 24 | } else { 25 | decl.init = null; 26 | } 27 | 28 | /* c8 ignore end */ 29 | node.declarations.push(this.finishNode(decl, 'VariableDeclarator')); 30 | 31 | if (!this.eat(tt.comma)) 32 | break; 33 | } 34 | 35 | return node; 36 | } 37 | }; 38 | } 39 | 40 | export const MissingInitializer = { 41 | missInitializer(isFor) { 42 | return this.parseMaybeAssign(isFor); 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /packages/keyword-missing-initializer/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordBrokenString from './index.js'; 3 | import operatorSafeAssignment from '../operator-safe-assignment/index.js'; 4 | 5 | const test = createTest(import.meta.url, keywordBrokenString); 6 | const testSafeAssignment = createTest(import.meta.url, ...[keywordBrokenString, operatorSafeAssignment]); 7 | 8 | test('goldstein: missing-initializer', (t) => { 9 | t.compile('missing-initializer'); 10 | t.end(); 11 | }); 12 | 13 | testSafeAssignment('goldstein: missing-initializer: safeAssignment', (t) => { 14 | t.compile('missing-initializer'); 15 | t.end(); 16 | }); 17 | 18 | test('goldstein: missing-initializer: no safeAssignment', (t) => { 19 | t.raise('no-safe-assignment', `Enable 'operator-safe-assignment' to have ability to use '?=' (1:26)`); 20 | t.end(); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/keyword-should/fixture/await-should.gs: -------------------------------------------------------------------------------- 1 | should await hello() -------------------------------------------------------------------------------- /packages/keyword-should/fixture/await-should.js: -------------------------------------------------------------------------------- 1 | try { 2 | await hello(); 3 | } catch {}; 4 | -------------------------------------------------------------------------------- /packages/keyword-should/fixture/not-supported.gs: -------------------------------------------------------------------------------- 1 | should { 2 | var a = "hello" 3 | console.log(a) 4 | } -------------------------------------------------------------------------------- /packages/keyword-should/fixture/should.gs: -------------------------------------------------------------------------------- 1 | should hello() -------------------------------------------------------------------------------- /packages/keyword-should/fixture/should.js: -------------------------------------------------------------------------------- 1 | try { 2 | hello(); 3 | } catch {}; 4 | -------------------------------------------------------------------------------- /packages/keyword-should/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | import { 3 | addKeyword, 4 | TokenType, 5 | tokTypes as tt, 6 | } from '../operator/index.js'; 7 | 8 | const { 9 | isCallExpression, 10 | isAwaitExpression, 11 | } = types; 12 | 13 | export default function newSpeak(Parser) { 14 | const {keywordTypes} = Parser.acorn; 15 | 16 | keywordTypes.should = new TokenType('should', { 17 | keyword: 'should', 18 | }); 19 | 20 | return class extends Parser { 21 | parse() { 22 | this.keywords = addKeyword('should', this.keywords); 23 | return super.parse(); 24 | } 25 | 26 | parseStatement(context, topLevel, exports) { 27 | if (this.type === keywordTypes.should) 28 | return this.parseShould(); 29 | 30 | return super.parseStatement(context, topLevel, exports); 31 | } 32 | 33 | parseShould() { 34 | this.next(); 35 | 36 | const node = super.startNode(); 37 | 38 | if (this.type === tt.braceL) 39 | return this.raise(this.start, `After 'should' only 'await' and 'function call' can come, brackets are not supported`); 40 | 41 | const expression = this.parseExpression(); 42 | 43 | if (isCallExpression(expression)) 44 | node.expression = { 45 | type: 'TryStatement', 46 | block: { 47 | type: 'BlockStatement', 48 | body: [{ 49 | type: 'ExpressionStatement', 50 | expression: { 51 | type: 'CallExpression', 52 | callee: expression.callee, 53 | arguments: expression.arguments.slice(), 54 | }, 55 | }], 56 | }, 57 | handler: { 58 | type: 'CatchClause', 59 | body: { 60 | type: 'BlockStatement', 61 | body: [], 62 | }, 63 | }, 64 | }; 65 | else if (isAwaitExpression(expression)) 66 | node.expression = { 67 | type: 'TryStatement', 68 | block: { 69 | type: 'BlockStatement', 70 | body: [{ 71 | type: 'ExpressionStatement', 72 | expression, 73 | }], 74 | }, 75 | handler: { 76 | type: 'CatchClause', 77 | body: { 78 | type: 'BlockStatement', 79 | body: [], 80 | }, 81 | }, 82 | }; 83 | 84 | return super.finishNode(node, 'ExpressionStatement'); 85 | } 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /packages/keyword-should/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: keyword: should', (t) => { 7 | t.compile('should'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: should (with await)', (t) => { 12 | t.compile('await-should'); 13 | t.end(); 14 | }); 15 | 16 | test('goldstein: keyword: should (brackets)', (t) => { 17 | t.raise('not-supported', `After 'should' only 'await' and 'function call' can come, brackets are not supported (1:7)`); 18 | t.end(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/keyword-throw/fixture/throw-statement.gs: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | throw 'hello'; 3 | } -------------------------------------------------------------------------------- /packages/keyword-throw/fixture/throw-statement.js: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | throw 'hello'; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/keyword-throw/fixture/throw.gs: -------------------------------------------------------------------------------- 1 | const a = () => throw 'hello'; -------------------------------------------------------------------------------- /packages/keyword-throw/fixture/throw.js: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | throw 'hello'; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/keyword-throw/index.js: -------------------------------------------------------------------------------- 1 | const {assign} = Object; 2 | 3 | export default function keywordThrow(Parser) { 4 | const {keywordTypes} = Parser.acorn; 5 | 6 | return class extends Parser { 7 | parseExprAtom(refDestructuringErrors, forInit) { 8 | if (this.type === keywordTypes.throw) 9 | return this.parseThrowExpression(); 10 | 11 | return super.parseExprAtom(refDestructuringErrors, forInit); 12 | } 13 | 14 | parseThrowExpression() { 15 | this.next(); 16 | 17 | const node = super.startNode(); 18 | const expression = this.parseExpression(); 19 | 20 | assign(node, { 21 | body: [{ 22 | type: 'ThrowStatement', 23 | argument: expression, 24 | }], 25 | }); 26 | 27 | return super.finishNode(node, 'BlockStatement'); 28 | } 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/keyword-throw/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordThrow from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordThrow); 5 | 6 | test('goldstein: keyword: throw', (t) => { 7 | t.compile('throw'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: throw-statement', (t) => { 12 | t.compile('throw-statement'); 13 | t.end(); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/await.gs: -------------------------------------------------------------------------------- 1 | try await hello(); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/await.js: -------------------------------------------------------------------------------- 1 | await tryToCatch(hello); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/fn.gs: -------------------------------------------------------------------------------- 1 | try await fn(); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/fn.js: -------------------------------------------------------------------------------- 1 | await tryToCatch(fn); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/no-catch.gs: -------------------------------------------------------------------------------- 1 | try { 2 | hello(); 3 | } -------------------------------------------------------------------------------- /packages/keyword-try/fixture/not-supported.gs: -------------------------------------------------------------------------------- 1 | try abc hello(); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/try-catch.gs: -------------------------------------------------------------------------------- 1 | try { 2 | hello(); 3 | } catch { 4 | 5 | } 6 | 7 | try { 8 | hello(); 9 | } catch(error) { 10 | 11 | } finally { 12 | 13 | } 14 | 15 | try { 16 | hello(); 17 | } catch({message}) { 18 | } 19 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/try-catch.js: -------------------------------------------------------------------------------- 1 | try { 2 | hello(); 3 | } catch {} 4 | 5 | try { 6 | hello(); 7 | } catch(error) {} finally {} 8 | 9 | try { 10 | hello(); 11 | } catch({message}) {} 12 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/try.gs: -------------------------------------------------------------------------------- 1 | try hello(); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/try.js: -------------------------------------------------------------------------------- 1 | tryCatch(hello); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/var.gs: -------------------------------------------------------------------------------- 1 | const [error, result] = try await hello(); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/fixture/var.js: -------------------------------------------------------------------------------- 1 | const [error, result] = await tryToCatch(hello); 2 | -------------------------------------------------------------------------------- /packages/keyword-try/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | import {setGoldsteinTry} from '../types/try.js'; 3 | import { 4 | BIND_LEXICAL, 5 | BIND_SIMPLE_CATCH, 6 | SCOPE_SIMPLE_CATCH, 7 | tokTypes as tt, 8 | } from '../operator/index.js'; 9 | 10 | const { 11 | isCallExpression, 12 | isAwaitExpression, 13 | } = types; 14 | 15 | export default function keywordTry(Parser) { 16 | const {keywordTypes} = Parser.acorn; 17 | 18 | return class extends Parser { 19 | parseExprAtom(refDestructuringErrors, forInit) { 20 | if (this.type === keywordTypes.try) 21 | return this.parseTryStatement(); 22 | 23 | return super.parseExprAtom(refDestructuringErrors, forInit); 24 | } 25 | 26 | parseTryStatement() { 27 | this.next(); 28 | 29 | const node = super.startNode(); 30 | 31 | if (this.type === tt.braceL) 32 | return this.parseUglyTry(node); 33 | 34 | const expression = this.parseExpression(); 35 | 36 | if (isCallExpression(expression)) 37 | node.expression = { 38 | type: 'CallExpression', 39 | callee: { 40 | type: 'Identifier', 41 | name: 'tryCatch', 42 | }, 43 | arguments: [ 44 | expression.callee, 45 | ...expression.arguments, 46 | ], 47 | }; 48 | else if (isAwaitExpression(expression)) 49 | node.expression = { 50 | type: 'AwaitExpression', 51 | argument: { 52 | type: 'CallExpression', 53 | callee: { 54 | type: 'Identifier', 55 | name: 'tryToCatch', 56 | }, 57 | arguments: [ 58 | expression.argument.callee, 59 | ...expression.argument.arguments, 60 | ], 61 | }, 62 | }; 63 | else 64 | this.raise(this.start, `After 'try' only '{', 'await' and 'function call' can come`); 65 | 66 | setGoldsteinTry(node.expression); 67 | 68 | return super.finishNode(node, 'ExpressionStatement'); 69 | } 70 | 71 | parseUglyTry(node) { 72 | node.block = this.parseBlock(); 73 | node.handler = null; 74 | 75 | if (this.type === tt._catch) { 76 | const clause = this.startNode(); 77 | this.next(); 78 | 79 | if (this.eat(tt.parenL)) { 80 | clause.param = this.parseBindingAtom(); 81 | const simple = clause.param.type === 'Identifier'; 82 | 83 | this.enterScope(simple ? SCOPE_SIMPLE_CATCH : 0); 84 | this.checkLValPattern(clause.param, simple ? BIND_SIMPLE_CATCH : BIND_LEXICAL); 85 | this.expect(tt.parenR); 86 | } else { 87 | clause.param = null; 88 | this.enterScope(0); 89 | } 90 | 91 | clause.body = this.parseBlock(false); 92 | this.exitScope(); 93 | node.handler = this.finishNode(clause, 'CatchClause'); 94 | } 95 | 96 | node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null; 97 | 98 | if (!node.handler && !node.finalizer) 99 | this.raise(node.start, 'Missing catch or finally clause'); 100 | 101 | setGoldsteinTry(node); 102 | 103 | return this.finishNode(node, 'TryStatement'); 104 | } 105 | }; 106 | } 107 | -------------------------------------------------------------------------------- /packages/keyword-try/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: keyword: try', (t) => { 7 | t.compile('try'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: keyword: try: await', (t) => { 12 | t.compile('await'); 13 | t.end(); 14 | }); 15 | 16 | test('goldstein: keyword: try: var', (t) => { 17 | t.compile('var'); 18 | t.end(); 19 | }); 20 | 21 | test('goldstein: keyword: try: try-catch', (t) => { 22 | t.compile('try-catch'); 23 | t.end(); 24 | }); 25 | 26 | test('goldstein: keyword: try: fn', (t) => { 27 | t.compile('fn'); 28 | t.end(); 29 | }); 30 | 31 | test('goldstein: keyword: try: not-supported', (t) => { 32 | t.raise('not-supported', `After 'try' only '{', 'await' and 'function call' can come (1:8)`); 33 | t.end(); 34 | }); 35 | 36 | test('goldstein: keyword: try: no-catch', (t) => { 37 | t.raise('no-catch', `Missing catch or finally clause (1:4)`); 38 | t.end(); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/keyword-useless-comma/fixture/useless-comma.gs: -------------------------------------------------------------------------------- 1 | const a = { 2 | b,, 3 | } -------------------------------------------------------------------------------- /packages/keyword-useless-comma/fixture/useless-comma.js: -------------------------------------------------------------------------------- 1 | const a = { 2 | b, 3 | , 4 | }; 5 | -------------------------------------------------------------------------------- /packages/keyword-useless-comma/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from 'acorn'; 2 | 3 | export default function fn(Parser) { 4 | return class extends Parser { 5 | parseIdentNode() { 6 | const node = this.startNode(); 7 | 8 | /* c8 ignore start */ 9 | if (this.type === tt.name) { 10 | node.name = this.value; 11 | } else if (this.type.keyword) { 12 | node.name = this.type.keyword; 13 | 14 | // To fix https://github.com/acornjs/acorn/issues/575 15 | // `class` and `function` keywords push new context into this.context. 16 | // But there is no chance to pop the context if the keyword is consumed as an identifier such as a property name. 17 | // If the previous token is a dot, this does not apply because the context-managing code already ignored the keyword 18 | if ((node.name === 'class' || node.name === 'function') && (this.lastTokEnd !== this.lastTokStart + 1 || this.input.charCodeAt(this.lastTokStart) !== 46)) 19 | this.context.pop(); 20 | 21 | this.type = tt.name; /* c8 ignore end */ 22 | } else { 23 | node.name = this.value; 24 | } 25 | 26 | return node; 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/keyword-useless-comma/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordUselessComma from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordUselessComma); 5 | 6 | test('goldstein: keyword: useless-comma', (t) => { 7 | t.compile('useless-comma'); 8 | t.end(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/keyword-useless-comma/remove-unnamed-object-property/index.js: -------------------------------------------------------------------------------- 1 | export const report = () => {}; 2 | export const fix = (path) => path.remove(); 3 | export const traverse = ({push}) => ({ 4 | 'ObjectProperty|ClassProperty'(path) { 5 | const keyPath = path.get('key'); 6 | 7 | if (keyPath.isIdentifier() && !keyPath.node.name) 8 | push(path); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/keyword-useless-comma/remove-unnamed-object-property/index.spec.js: -------------------------------------------------------------------------------- 1 | import {test} from 'supertape'; 2 | import montag from 'montag'; 3 | import {transform} from 'putout'; 4 | import * as removeUnnamedIdentifier from './index.js'; 5 | import {parse, print} from '../../goldstein/index.js'; 6 | import keywordUselessComma from '../index.js'; 7 | 8 | test('goldstein: keyword: useless-comma: remove-unnamed-object-property', (t) => { 9 | const source = montag` 10 | const a = { 11 | b,, 12 | }; 13 | `; 14 | 15 | const ast = parse(source, { 16 | keywords: [ 17 | keywordUselessComma, 18 | ], 19 | }); 20 | 21 | transform(ast, source, { 22 | plugins: [ 23 | ['remove', removeUnnamedIdentifier], 24 | ], 25 | }); 26 | 27 | const code = print(ast); 28 | 29 | const expected = montag` 30 | const a = { 31 | b, 32 | };\n 33 | `; 34 | 35 | t.equal(code, expected); 36 | t.end(); 37 | }); 38 | 39 | test('goldstein: keyword: useless-comma: remove-unnamed-object-property: StringLiteral', (t) => { 40 | const source = montag` 41 | const a = { 42 | "hello": "world",, 43 | }; 44 | `; 45 | 46 | const ast = parse(source, { 47 | keywords: [ 48 | keywordUselessComma, 49 | ], 50 | }); 51 | 52 | transform(ast, source, { 53 | plugins: [ 54 | ['rename', removeUnnamedIdentifier], 55 | ], 56 | }); 57 | 58 | const code = print(ast); 59 | 60 | const expected = montag` 61 | const a = { 62 | 'hello': 'world', 63 | };\n 64 | `; 65 | 66 | t.equal(code, expected); 67 | t.end(); 68 | }); 69 | 70 | test('goldstein: keyword: useless-comma: remove-unnamed-class-property', (t) => { 71 | const source = montag` 72 | const a = class { 73 | b() {}, 74 | c() {}, 75 | }; 76 | `; 77 | 78 | const ast = parse(source, { 79 | keywords: [ 80 | keywordUselessComma, 81 | ], 82 | }); 83 | 84 | transform(ast, source, { 85 | plugins: [ 86 | ['remove', removeUnnamedIdentifier], 87 | ], 88 | }); 89 | 90 | const code = print(ast); 91 | 92 | const expected = montag` 93 | const a = class { 94 | b() {} 95 | 96 | c() {} 97 | };\n 98 | `; 99 | 100 | t.equal(code, expected); 101 | t.end(); 102 | }); 103 | -------------------------------------------------------------------------------- /packages/keyword-useless-semicolon/fixture/useless-semicolon.gs: -------------------------------------------------------------------------------- 1 | const c = { 2 | b; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/keyword-useless-semicolon/fixture/useless-semicolon.js: -------------------------------------------------------------------------------- 1 | const c = { 2 | b, 3 | }; 4 | -------------------------------------------------------------------------------- /packages/keyword-useless-semicolon/index.js: -------------------------------------------------------------------------------- 1 | import {tokTypes as tt} from 'acorn'; 2 | 3 | export default function fn(Parser) { 4 | return class extends Parser { 5 | parseObj(isPattern, refDestructuringErrors) { 6 | const node = this.startNode(); 7 | let first = true; 8 | const propHash = {}; 9 | 10 | node.properties = []; 11 | this.next(); 12 | 13 | while (!this.eat(tt.braceR)) { 14 | if (!first) { 15 | this.eat(tt.comma); 16 | this.eat(tt.semi); 17 | 18 | if (this.options.ecmaVersion >= 5 && this.afterTrailingComma(tt.braceR)) 19 | break; 20 | } else { 21 | first = false; 22 | } 23 | 24 | const prop = this.parseProperty(isPattern, refDestructuringErrors); 25 | 26 | if (!isPattern) 27 | this.checkPropClash(prop, propHash, refDestructuringErrors); 28 | 29 | node.properties.push(prop); 30 | } 31 | 32 | /* c8 ignore start */ 33 | return this.finishNode(node, isPattern ? 'ObjectPattern' : 'ObjectExpression'); /* c8 ignore end */ 34 | } 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /packages/keyword-useless-semicolon/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordUselessComma from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordUselessComma); 5 | 6 | test('goldstein: keyword: useless-semicolon', (t) => { 7 | t.compile('useless-semicolon'); 8 | t.end(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/operator-safe-assignment/fixture/not-supported.gs: -------------------------------------------------------------------------------- 1 | const a ?= 5; -------------------------------------------------------------------------------- /packages/operator-safe-assignment/fixture/safe-assignment.gs: -------------------------------------------------------------------------------- 1 | const [error, response] ?= await fetch("https://arthur.place"); 2 | const [error1, response1] ?= fn("https://arthur.place"); 3 | -------------------------------------------------------------------------------- /packages/operator-safe-assignment/fixture/safe-assignment.js: -------------------------------------------------------------------------------- 1 | const [error, response] = await tryToCatch(fetch, 'https://arthur.place'); 2 | const [error1, response1] = tryCatch(fn, 'https://arthur.place'); 3 | -------------------------------------------------------------------------------- /packages/operator-safe-assignment/index.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | import {setGoldsteinTry} from '../types/try.js'; 3 | import {MissingInitializer} from '../keyword-missing-initializer/index.js'; 4 | import {tokTypes as tt} from '../operator/index.js'; 5 | 6 | const {assign} = Object; 7 | 8 | const { 9 | isCallExpression, 10 | isAwaitExpression, 11 | } = types; 12 | 13 | export default function operatorSafeAssignment(Parser) { 14 | return class extends Parser { 15 | parseVar(node, isFor, kind, allowMissingInitializer) { 16 | node.declarations = []; 17 | node.kind = kind; 18 | for (;;) { 19 | const decl = this.startNode(); 20 | this.parseVarId(decl, kind); 21 | 22 | if (this.eat(tt.question) && this.eat(tt.eq)) 23 | decl.init = this.parseSafeAssignment(); /* c8 ignore start */ 24 | else if (this.eat(tt.eq)) 25 | decl.init = this.parseMaybeAssign(isFor); 26 | else if (!allowMissingInitializer && kind === 'const' && this.type !== tt._in && !(this.options.ecmaVersion >= 6 && this.isContextual('of'))) 27 | decl.init = MissingInitializer.missInitializer.call(this, isFor); 28 | else if (!allowMissingInitializer && decl.id.type !== 'Identifier' && !(isFor && (this.type === tt._in || this.isContextual('of')))) 29 | this.raise(this.lastTokEnd, 'Complex binding patterns require an initialization value'); 30 | else 31 | decl.init = null; 32 | 33 | /* c8 ignore end */ 34 | node.declarations.push(this.finishNode(decl, 'VariableDeclarator')); 35 | 36 | if (!this.eat(tt.comma)) 37 | break; 38 | } 39 | 40 | return node; 41 | } 42 | 43 | parseSafeAssignment() { 44 | const node = super.startNode(); 45 | const expression = this.parseExpression(); 46 | 47 | if (isCallExpression(expression)) 48 | assign(node, { 49 | type: 'CallExpression', 50 | callee: { 51 | type: 'Identifier', 52 | name: 'tryCatch', 53 | }, 54 | arguments: [ 55 | expression.callee, 56 | ...expression.arguments, 57 | ], 58 | }); 59 | else if (isAwaitExpression(expression)) 60 | assign(node, { 61 | type: 'AwaitExpression', 62 | argument: { 63 | type: 'CallExpression', 64 | callee: { 65 | type: 'Identifier', 66 | name: 'tryToCatch', 67 | }, 68 | arguments: [ 69 | expression.argument.callee, 70 | ...expression.argument.arguments, 71 | ], 72 | }, 73 | }); 74 | else 75 | this.raise(this.start, `After '?=' only 'await' and 'function call' can come`); 76 | 77 | setGoldsteinTry(node); 78 | return super.finishNode(node, node.type); 79 | } 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /packages/operator-safe-assignment/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import operatorSafeAssignment from './index.js'; 3 | import keywordMissingInitializer from '../keyword-missing-initializer/index.js'; 4 | 5 | const test = createTest(import.meta.url, ...[operatorSafeAssignment, keywordMissingInitializer]); 6 | 7 | test('goldstein: operator: safe-assignment', (t) => { 8 | t.compile('safe-assignment'); 9 | t.end(); 10 | }); 11 | 12 | test('goldstein: operator: safe-assignment: not-supported', (t) => { 13 | t.raise('not-supported', `After '?=' only 'await' and 'function call' can come (1:12)`); 14 | t.end(); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/operator/index.js: -------------------------------------------------------------------------------- 1 | export * from 'acorn'; 2 | export * from './scopeflags.js'; 3 | export function addKeyword(keyword, keywords) { 4 | const str = keywords 5 | .toString() 6 | .replace(')$', `|${keyword})$`) 7 | .slice(1, -1); 8 | 9 | return RegExp(str); 10 | } 11 | 12 | export class DestructuringErrors { 13 | shorthandAssign = -1; 14 | trailingComma = -1; 15 | parenthesizedAssign = -1; 16 | parenthesizedBind = -1; 17 | doubleProto = -1; 18 | } 19 | -------------------------------------------------------------------------------- /packages/operator/scopeflags.js: -------------------------------------------------------------------------------- 1 | // Each scope gets a bitset that may contain these flags 2 | export const SCOPE_TOP = 1, 3 | SCOPE_FUNCTION = 2, 4 | SCOPE_ASYNC = 4, 5 | SCOPE_GENERATOR = 8, 6 | SCOPE_ARROW = 16, 7 | SCOPE_SIMPLE_CATCH = 32, 8 | SCOPE_SUPER = 64, 9 | SCOPE_DIRECT_SUPER = 128, 10 | SCOPE_CLASS_STATIC_BLOCK = 256, 11 | SCOPE_VAR = SCOPE_TOP | SCOPE_FUNCTION | SCOPE_CLASS_STATIC_BLOCK; 12 | 13 | // Used in checkLVal* and declareName to determine the type of a binding 14 | export const BIND_NONE = 0, // Not a binding 15 | BIND_VAR = 1, // Var-style binding 16 | BIND_LEXICAL = 2, // Let- or const-style binding 17 | BIND_FUNCTION = 3, // Function declaration 18 | BIND_SIMPLE_CATCH = 4, // Simple (identifier pattern) catch binding 19 | BIND_OUTSIDE = 5; // Special case for function names as bound inside the function 20 | -------------------------------------------------------------------------------- /packages/parser/index.js: -------------------------------------------------------------------------------- 1 | import {Parser} from 'acorn'; 2 | import {attachComments} from 'estree-util-attach-comments'; 3 | 4 | export const extendParser = (keywords) => { 5 | const parser = Parser.extend(...keywords); 6 | const parse = createParse(parser); 7 | 8 | return { 9 | parse, 10 | }; 11 | }; 12 | 13 | const createParse = (parser) => (source) => { 14 | const comments = []; 15 | const options = { 16 | ecmaVersion: 'latest', 17 | sourceType: 'module', 18 | locations: true, 19 | comment: true, 20 | ranges: true, 21 | onComment: comments, 22 | preserveParens: true, 23 | allowHashBang: true, 24 | }; 25 | 26 | const ast = parser.parse(source, options); 27 | 28 | attachComments(ast, comments); 29 | 30 | return { 31 | ...ast, 32 | tokens: Array.from(parser.tokenizer(source, options)), 33 | comments, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/printer/index.js: -------------------------------------------------------------------------------- 1 | import {print as printJS} from '@putout/printer'; 2 | import {fixEmpty} from '../goldstein/index.js'; 3 | import {AwaitExpression} from './visitors/await-expression.js'; 4 | import {CallExpression} from './visitors/call-expression.js'; 5 | import {TryStatement} from './visitors/try-statement.js'; 6 | import {IfStatement} from './visitors/if-statement.js'; 7 | 8 | export const print = (ast) => { 9 | const code = printJS(ast, { 10 | visitors: { 11 | CallExpression, 12 | TryStatement, 13 | AwaitExpression, 14 | IfStatement, 15 | }, 16 | }); 17 | 18 | return fixEmpty(code); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/printer/index.spec.js: -------------------------------------------------------------------------------- 1 | import {test} from 'supertape'; 2 | import montag from 'montag'; 3 | import {estreeToBabel} from 'estree-to-babel'; 4 | import {print} from './index.js'; 5 | import {parse} from '../goldstein/index.js'; 6 | 7 | test('goldstein: printer: try: await', (t) => { 8 | const source = `const a = try await f('hello')`; 9 | const ast = estreeToBabel(parse(source)); 10 | const result = print(ast); 11 | 12 | const expected = montag` 13 | const a = try await f('hello');\n 14 | `; 15 | 16 | t.equal(result, expected); 17 | t.end(); 18 | }); 19 | 20 | test('goldstein: printer: try', (t) => { 21 | const source = `const a = try f('hello')`; 22 | const ast = estreeToBabel(parse(source)); 23 | const result = print(ast); 24 | 25 | const expected = montag` 26 | const a = try f('hello');\n 27 | `; 28 | 29 | t.equal(result, expected); 30 | t.end(); 31 | }); 32 | 33 | test('goldstein: printer: if', (t) => { 34 | const source = montag` 35 | if a > 3 { 36 | console.log('x'); 37 | } 38 | `; 39 | 40 | const ast = estreeToBabel(parse(source)); 41 | const result = print(ast); 42 | 43 | const expected = montag` 44 | if a > 3 { 45 | console.log('x'); 46 | }\n 47 | `; 48 | 49 | t.equal(result, expected); 50 | t.end(); 51 | }); 52 | 53 | test('goldstein: printer: if: else', (t) => { 54 | const source = montag` 55 | if a > 3 { 56 | console.log('x'); 57 | } else { 58 | console.log('y'); 59 | } 60 | `; 61 | 62 | const ast = estreeToBabel(parse(source)); 63 | const result = print(ast); 64 | 65 | const expected = montag` 66 | if a > 3 { 67 | console.log('x'); 68 | } else { 69 | console.log('y'); 70 | }\n 71 | `; 72 | 73 | t.equal(result, expected); 74 | t.end(); 75 | }); 76 | -------------------------------------------------------------------------------- /packages/printer/visitors/await-expression.js: -------------------------------------------------------------------------------- 1 | import { 2 | maybeVisitor, 3 | visitors as v, 4 | } from '@putout/printer'; 5 | 6 | export const AwaitExpression = (path, printer, semantics) => { 7 | const {print} = printer; 8 | 9 | if (!path.node.goldstein) 10 | return maybeVisitor(v.AwaitExpression, path, printer, semantics); 11 | 12 | print('__goldstein'); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/printer/visitors/await-expression.spec.js: -------------------------------------------------------------------------------- 1 | import {test} from 'supertape'; 2 | import montag from 'montag'; 3 | import {estreeToBabel} from 'estree-to-babel'; 4 | import {print} from '../index.js'; 5 | import {parse} from '../../goldstein/index.js'; 6 | 7 | test('goldstein: printer: visitors: await', (t) => { 8 | const source = `const a = await f('hello')`; 9 | const ast = estreeToBabel(parse(source)); 10 | const result = print(ast); 11 | 12 | const expected = montag` 13 | const a = await f('hello');\n 14 | `; 15 | 16 | t.equal(result, expected); 17 | t.end(); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/printer/visitors/call-expression.js: -------------------------------------------------------------------------------- 1 | import { 2 | maybeVisitor, 3 | visitors as v, 4 | } from '@putout/printer'; 5 | 6 | export const CallExpression = (path, printer, semantics) => { 7 | const {print} = printer; 8 | 9 | if (!path.node.goldstein) 10 | return maybeVisitor(v.CallExpression, path, printer, semantics); 11 | 12 | return print('__goldstein'); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/printer/visitors/if-statement.js: -------------------------------------------------------------------------------- 1 | export const IfStatement = (path, printer) => { 2 | const {print, indent} = printer; 3 | const {node} = path; 4 | 5 | indent(); 6 | print('if '); 7 | 8 | const testPath = path.get('test'); 9 | 10 | if (testPath.isVariableDeclaration()) { 11 | const first = testPath.get('declarations.0'); 12 | const id = first.get('id'); 13 | const init = first.get('init'); 14 | 15 | print(testPath.node.kind); 16 | print(' '); 17 | print(id); 18 | print(' = '); 19 | print(init); 20 | } else { 21 | print('__test'); 22 | } 23 | 24 | print(' '); 25 | print('__consequent'); 26 | 27 | if (node.alternate) { 28 | print(' else '); 29 | print('__alternate'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /packages/printer/visitors/try-statement.js: -------------------------------------------------------------------------------- 1 | import { 2 | visitors as v, 3 | maybeVisitor, 4 | } from '@putout/printer'; 5 | 6 | export const TryStatement = (path, printer, semantics) => { 7 | const {maybe, print} = printer; 8 | const {node} = path; 9 | 10 | if (!node.expression) 11 | return maybeVisitor(v.TryStatement, path, printer, semantics); 12 | 13 | print('try '); 14 | maybe.print(node.async, 'await '); 15 | print('__argument'); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/printer/visitors/try-statement.spec.js: -------------------------------------------------------------------------------- 1 | import {test} from 'supertape'; 2 | import montag from 'montag'; 3 | import {estreeToBabel} from 'estree-to-babel'; 4 | import {print} from '../index.js'; 5 | import {parse} from '../../goldstein/index.js'; 6 | 7 | test('goldstein: printer: visitors: try: await', (t) => { 8 | const source = `const a = try await f('hello')`; 9 | const ast = estreeToBabel(parse(source)); 10 | const result = print(ast); 11 | 12 | const expected = montag` 13 | const a = try await f('hello');\n 14 | `; 15 | 16 | t.equal(result, expected); 17 | t.end(); 18 | }); 19 | 20 | test('goldstein: printer: visitors: try', (t) => { 21 | const source = `const a = try f('hello')`; 22 | const ast = estreeToBabel(parse(source)); 23 | const result = print(ast); 24 | 25 | const expected = montag` 26 | const a = try f('hello');\n 27 | `; 28 | 29 | t.equal(result, expected); 30 | t.end(); 31 | }); 32 | 33 | test('goldstein: printer: visitors: try-catch', (t) => { 34 | const source = `try {} catch {}`; 35 | const ast = estreeToBabel(parse(source)); 36 | const result = print(ast); 37 | 38 | const expected = 'try {} catch {}\n'; 39 | 40 | t.equal(result, expected); 41 | t.end(); 42 | }); 43 | 44 | test('goldstein: printer: visitors: try catch', (t) => { 45 | const source = `try {} catch {}`; 46 | const ast = parse(source); 47 | const result = print(ast); 48 | 49 | t.equal(result, `${source}\n`); 50 | t.end(); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/string-interpolation/fixture/number.gs: -------------------------------------------------------------------------------- 1 | const x = 5; 2 | -------------------------------------------------------------------------------- /packages/string-interpolation/fixture/simple.gs: -------------------------------------------------------------------------------- 1 | let what = 'String Interpolation' 2 | let str = "This is a /(what.toUpperCase())" -------------------------------------------------------------------------------- /packages/string-interpolation/fixture/simple.js: -------------------------------------------------------------------------------- 1 | let what = 'String Interpolation'; 2 | let str = `This is a ${what.toUpperCase()}`; 3 | -------------------------------------------------------------------------------- /packages/string-interpolation/index.js: -------------------------------------------------------------------------------- 1 | import {template} from 'putout'; 2 | 3 | const isString = (a) => typeof a === 'string'; 4 | const {assign} = Object; 5 | 6 | export default function stringInterpolation(Parser) { 7 | return class extends Parser { 8 | parseLiteral(value) { 9 | if (!isString(value)) 10 | return super.parseLiteral(value); 11 | 12 | const chars = value.split(''); 13 | 14 | let literalOpened = false; 15 | let parenthesis = 0; 16 | const out = []; 17 | let isTemplateLiteral = false; 18 | 19 | for (let [index, char] of chars.entries()) { 20 | if (char === '(') { 21 | // keep count of parenthesis 22 | parenthesis++; 23 | // check if previous token was "/" (only if literal is not opened yet) 24 | const prev = index - 1; 25 | 26 | if (chars[prev] === '/' && !literalOpened) { 27 | // set previous char to "$" 28 | out[prev] = '$'; 29 | // set current char to "{" 30 | char = '{'; 31 | // set literalOpened to true 32 | literalOpened = true; 33 | // match TemplateLiteral instead of StringLiteral 34 | isTemplateLiteral = true; 35 | } 36 | } else if (char === ')') { 37 | parenthesis--; 38 | 39 | if (!parenthesis && literalOpened) { 40 | char = '}'; 41 | // reset literalOpened to false 42 | literalOpened = false; 43 | } 44 | } 45 | 46 | out.push(char); 47 | } 48 | 49 | if (isTemplateLiteral) { 50 | const node = this.startNode(); 51 | this.next(); 52 | 53 | const {quasis, expressions} = template.ast(`\`${out.join('')}\``); 54 | 55 | assign(node, { 56 | quasis, 57 | expressions, 58 | }); 59 | 60 | return this.finishNode(node, 'TemplateLiteral'); 61 | } 62 | 63 | return super.parseLiteral(out.join('')); 64 | } 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /packages/string-interpolation/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '../test/index.js'; 2 | import keywordFn from './index.js'; 3 | 4 | const test = createTest(import.meta.url, keywordFn); 5 | 6 | test('goldstein: string-interpolation: simple', (t) => { 7 | t.compile('simple'); 8 | t.end(); 9 | }); 10 | 11 | test('goldstein: string-interpolation: number', (t) => { 12 | t.noCompile('number'); 13 | t.end(); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/test/index.js: -------------------------------------------------------------------------------- 1 | import {readFileSync, writeFileSync} from 'node:fs'; 2 | import {fileURLToPath} from 'node:url'; 3 | import {dirname, join} from 'node:path'; 4 | import process from 'node:process'; 5 | import {extend} from 'supertape'; 6 | import {print} from 'putout'; 7 | import tryCatch from 'try-catch'; 8 | import {estreeToBabel} from 'estree-to-babel'; 9 | import {extendParser} from '../parser/index.js'; 10 | import {fixEmpty} from '../goldstein/index.js'; 11 | 12 | const {stringify} = JSON; 13 | const {UPDATE, AST} = process.env; 14 | 15 | export const createTest = (url, ...keywords) => { 16 | const filename = fileURLToPath(url); 17 | const dir = dirname(filename); 18 | 19 | const parser = extendParser(keywords); 20 | 21 | return extend({ 22 | compile: compile({ 23 | dir, 24 | parser, 25 | }), 26 | noCompile: noCompile({ 27 | dir, 28 | parser, 29 | }), 30 | raise: raise({ 31 | dir, 32 | parser, 33 | }), 34 | }); 35 | }; 36 | 37 | const compile = ({dir, parser}) => (t) => (name) => { 38 | const from = join(dir, 'fixture', `${name}.gs`); 39 | const to = join(dir, 'fixture', `${name}.js`); 40 | const fromData = readFileSync(from, 'utf8'); 41 | const toData = readFileSync(to, 'utf8'); 42 | 43 | const ast = estreeToBabel(parser.parse(fromData)); 44 | 45 | if (AST === '1') 46 | process.stdout.write(stringify(ast, null, 4)); 47 | 48 | const result = fixEmpty(print(ast)); 49 | 50 | if (UPDATE === '1') { 51 | writeFileSync(to, result); 52 | return t.pass('update fixture'); 53 | } 54 | 55 | return t.equal(result, toData); 56 | }; 57 | 58 | const noCompile = ({dir, parser}) => (t) => (name) => { 59 | const from = join(dir, 'fixture', `${name}.gs`); 60 | const fromData = readFileSync(from, 'utf8'); 61 | 62 | const ast = estreeToBabel(parser.parse(fromData)); 63 | 64 | if (AST === '1') 65 | process.stdout.write(stringify(ast, null, 4)); 66 | 67 | const result = fixEmpty(print(ast)); 68 | 69 | return t.equal(result, fromData); 70 | }; 71 | 72 | const raise = ({dir, parser}) => (t) => (name, message) => { 73 | const from = join(dir, 'fixture', `${name}.gs`); 74 | const fromData = readFileSync(from, 'utf8'); 75 | 76 | const [error] = tryCatch(parser.parse, fromData); 77 | 78 | if (!error) 79 | return t.fail('No raise happened'); 80 | 81 | return t.equal(error.message, message); 82 | }; 83 | -------------------------------------------------------------------------------- /packages/types/if.js: -------------------------------------------------------------------------------- 1 | export const setGoldsteinIf = (node) => { 2 | node.goldsteinIf = true; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/types/try.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | 3 | const { 4 | isAwaitExpression, 5 | isCallExpression, 6 | } = types; 7 | 8 | export function createGoldsteinTry({async, callee, args}) { 9 | return { 10 | type: 'TryStatement', 11 | expression: true, 12 | async, 13 | argument: { 14 | type: 'CallExpression', 15 | callee, 16 | arguments: args, 17 | }, 18 | }; 19 | } 20 | 21 | export const setGoldsteinTry = (node) => { 22 | if (!isCallExpression(node) && !isAwaitExpression(node)) 23 | return; 24 | 25 | node.goldstein = createGoldsteinTry(parseExpression(node)); 26 | }; 27 | 28 | const parseExpression = (node) => { 29 | const {argument, arguments: allArgs} = node; 30 | 31 | const async = isAwaitExpression(node); 32 | 33 | if (!async) { 34 | const [callee, ...args] = allArgs; 35 | 36 | return { 37 | async, 38 | args, 39 | callee, 40 | }; 41 | } 42 | 43 | const [callee, ...args] = argument.arguments; 44 | 45 | return { 46 | async, 47 | args, 48 | callee, 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /rules/goldstein/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | yarn-error.log 4 | 5 | coverage 6 | .idea 7 | -------------------------------------------------------------------------------- /rules/goldstein/.madrun.js: -------------------------------------------------------------------------------- 1 | import {run} from 'madrun'; 2 | 3 | export default { 4 | 'prepublishOnly': () => run(['lint', 'test']), 5 | 'test': () => `tape 'test/*.js' 'lib/**/*.spec.js'`, 6 | 'watch:test': async () => `nodemon -w lib -x "${await run('test')}"`, 7 | 'lint': () => 'putout .', 8 | 'fresh:lint': async () => await run('lint', '--fresh'), 9 | 'lint:fresh': async () => await run('lint', '--fresh'), 10 | 'fix:lint': async () => await run('lint', '--fix'), 11 | 'coverage': async () => `c8 ${await run('test')}`, 12 | 'report': () => 'c8 report --reporter=lcov', 13 | }; 14 | -------------------------------------------------------------------------------- /rules/goldstein/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.spec.js 3 | test 4 | fixture 5 | yarn-error.log 6 | coverage 7 | *.config.* 8 | -------------------------------------------------------------------------------- /rules/goldstein/.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "check-coverage": true, 3 | "all": true, 4 | "exclude": [ 5 | "**/*.spec.*", 6 | "**/fixture", 7 | "test", 8 | ".*.*", 9 | "**/*.config.*" 10 | ], 11 | "branches": 100, 12 | "lines": 100, 13 | "functions": 100, 14 | "statements": 100 15 | } 16 | -------------------------------------------------------------------------------- /rules/goldstein/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) coderaiser 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 | -------------------------------------------------------------------------------- /rules/goldstein/README.md: -------------------------------------------------------------------------------- 1 | # @putout/plugin-goldstein [![NPM version][NPMIMGURL]][NPMURL] 2 | 3 | [NPMIMGURL]: https://img.shields.io/npm/v/@putout/plugin-goldstein.svg?style=flat&longCache=true 4 | [NPMURL]: https://npmjs.org/package/@putout/plugin-goldstein "npm" 5 | 6 | > JavaScript with no limits 🤫. 7 | > 8 | > (c) [**Goldstein**](https://github.com/coderaiser/goldstein) 9 | 10 | 🐊[**Putout**](https://github.com/coderaiser/putout) plugin adds ability to fix issues with 11 | **Madrun** config file. 12 | 13 | ## Install 14 | 15 | ``` 16 | npm i putout @putout/plugin-goldstein -D 17 | ``` 18 | 19 | ## Rules 20 | 21 | ```json 22 | { 23 | "rules": { 24 | "goldstein/convert-t-compile-to-compile": "on" 25 | } 26 | } 27 | ``` 28 | 29 | ## convert-t-compile-to-compile 30 | 31 | Checkout in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/a10dd187dc3c6be8df110a8481b9d9bc/3abfc0879aad5ccff5c8e4851cb3ad06270c986e). 32 | 33 | ### ❌ Example of incorrect code 34 | 35 | ```js 36 | test('goldstein: keyword: import', async ({compile}) => { 37 | await compile('import'); 38 | }); 39 | ``` 40 | 41 | ### ✅ Example of correct code 42 | 43 | ```js 44 | test('goldstein: keyword: import', async ({compile}) => { 45 | await compile('import'); 46 | }); 47 | ``` 48 | 49 | ## convert-t-compile-to-compile 50 | 51 | Checkout in 🐊[**Putout Editor**](https://putout.cloudcmd.io/#/gist/5272c99178bbf9e75f30177e7e9c15a9/9a3e81a9e9dd756915cbecff32acc5c50cddeca3). 52 | 53 | ### ❌ Example of incorrect code 54 | 55 | ```js 56 | test('goldstein: keyword: try: not-supported', async ({raise}) => { 57 | await raise('not-supported', `After 'try' only '{', 'await' and 'function call' can come (1:8)`); 58 | }); 59 | ``` 60 | 61 | ### ✅ Example of correct code 62 | 63 | ```js 64 | test('goldstein: keyword: try: not-supported', async ({raise}) => { 65 | await raise('not-supported', `After 'try' only '{', 'await' and 'function call' can come (1:8)`); 66 | }); 67 | ``` 68 | 69 | ## License 70 | 71 | MIT 72 | -------------------------------------------------------------------------------- /rules/goldstein/eslint.config.js: -------------------------------------------------------------------------------- 1 | import {safeAlign} from 'eslint-plugin-putout'; 2 | 3 | export default safeAlign; 4 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-compile-to-compile/fixture/convert-t-compile-to-compile-fix.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: import', async ({compile}) => { 2 | await compile('import'); 3 | }); 4 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-compile-to-compile/fixture/convert-t-compile-to-compile.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: import', (t) => { 2 | t.compile('import'); 3 | t.end(); 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-compile-to-compile/fixture/done.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: import', ({compile}) => { 2 | compile('import'); 3 | }); 4 | 5 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-compile-to-compile/index.js: -------------------------------------------------------------------------------- 1 | import {transform} from '../transform.js'; 2 | 3 | export const report = () => `Use 'compile()' instead of 't.compile()'`; 4 | 5 | export const replace = () => ({ 6 | 't.end()': '', 7 | 't.compile(__a)': transform('compile', '__a'), 8 | }); 9 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-compile-to-compile/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '@putout/test'; 2 | import * as plugin from './index.js'; 3 | 4 | const test = createTest(import.meta.url, { 5 | printer: 'putout', 6 | plugins: [ 7 | ['convert-t-compile-to-compile', plugin], 8 | ], 9 | }); 10 | 11 | test('rules: convert-t-compile-to-compile: report', (t) => { 12 | t.report('convert-t-compile-to-compile', `Use 'compile()' instead of 't.compile()'`); 13 | t.end(); 14 | }); 15 | 16 | test('rules: convert-t-compile-to-compile: transform', (t) => { 17 | t.transform('convert-t-compile-to-compile'); 18 | t.end(); 19 | }); 20 | 21 | test('rules: convert-t-compile-to-compile: no report: done', (t) => { 22 | t.noReport('done'); 23 | t.end(); 24 | }); 25 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-raise-to-raise/fixture/convert-t-raise-to-raise-fix.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: try: not-supported', async ({raise}) => { 2 | await raise('not-supported', `After 'try' only '{', 'await' and 'function call' can come (1:8)`); 3 | }); 4 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-raise-to-raise/fixture/convert-t-raise-to-raise.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: try: not-supported', (t) => { 2 | t.raise('not-supported', `After 'try' only '{', 'await' and 'function call' can come (1:8)`); 3 | }); 4 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-raise-to-raise/index.js: -------------------------------------------------------------------------------- 1 | import {transform} from '../transform.js'; 2 | 3 | export const report = () => `Use 'raise()' instead of 't.raise()'`; 4 | 5 | export const replace = () => ({ 6 | 't.end()': '', 7 | 't.raise(__a, __b)': transform('raise', '__a, __b'), 8 | }); 9 | -------------------------------------------------------------------------------- /rules/goldstein/lib/convert-t-raise-to-raise/index.spec.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '@putout/test'; 2 | import * as plugin from './index.js'; 3 | 4 | const test = createTest(import.meta.url, { 5 | printer: 'putout', 6 | plugins: [ 7 | ['convert-t-raise-to-raise', plugin], 8 | ], 9 | }); 10 | 11 | test('rules: convert-t-raise-to-raise: report', (t) => { 12 | t.report('convert-t-raise-to-raise', `Use 'raise()' instead of 't.raise()'`); 13 | t.end(); 14 | }); 15 | 16 | test('rules: convert-t-raise-to-raise: transform', (t) => { 17 | t.transform('convert-t-raise-to-raise'); 18 | t.end(); 19 | }); 20 | -------------------------------------------------------------------------------- /rules/goldstein/lib/index.js: -------------------------------------------------------------------------------- 1 | import * as convertTCompileToCompile from './convert-t-compile-to-compile/index.js'; 2 | import * as convertTRaiseToRaise from './convert-t-raise-to-raise/index.js'; 3 | 4 | export const rules = { 5 | 'convert-t-compile-to-compile': convertTCompileToCompile, 6 | 'convert-t-raise-to-raise': convertTRaiseToRaise, 7 | }; 8 | -------------------------------------------------------------------------------- /rules/goldstein/lib/transform.js: -------------------------------------------------------------------------------- 1 | import {types} from 'putout'; 2 | 3 | const { 4 | identifier, 5 | objectPattern, 6 | objectProperty, 7 | } = types; 8 | 9 | const COMPUTED = false; 10 | const SHORTHAND = true; 11 | 12 | export const transform = (name, args) => (vars, path) => { 13 | const {block} = path.scope; 14 | 15 | path.scope.block.async = true; 16 | 17 | const id = identifier(name); 18 | const param = objectPattern([ 19 | objectProperty(id, id, COMPUTED, SHORTHAND), 20 | ]); 21 | 22 | block.params = [param]; 23 | 24 | return `await ${name}(${args})`; 25 | }; 26 | -------------------------------------------------------------------------------- /rules/goldstein/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@putout/plugin-goldstein", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "author": "coderaiser (https://github.com/coderaiser)", 6 | "description": "🐊Putout plugin adds ability to transform madrun scripts", 7 | "homepage": "https://github.com/coderaiser/putout/tree/master/packages/plugin-madrun#readme", 8 | "main": "lib/index.js", 9 | "release": false, 10 | "tag": false, 11 | "changelog": false, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/coderaiser/putout.git" 15 | }, 16 | "scripts": { 17 | "test": "madrun test", 18 | "watch:test": "madrun watch:test", 19 | "lint": "madrun lint", 20 | "fresh:lint": "madrun fresh:lint", 21 | "lint:fresh": "madrun lint:fresh", 22 | "fix:lint": "madrun fix:lint", 23 | "coverage": "madrun coverage", 24 | "report": "madrun report" 25 | }, 26 | "dependencies": {}, 27 | "keywords": [ 28 | "putout", 29 | "putout-plugin", 30 | "plugin", 31 | "madrun" 32 | ], 33 | "devDependencies": { 34 | "@putout/test": "^9.1.0", 35 | "c8": "^10.0.0", 36 | "eslint": "^9.2.0", 37 | "eslint-plugin-putout": "^22.7.0", 38 | "madrun": "^9.0.0", 39 | "nodemon": "^3.0.1" 40 | }, 41 | "peerDependencies": { 42 | "putout": ">=31" 43 | }, 44 | "license": "MIT", 45 | "engines": { 46 | "node": ">=16" 47 | }, 48 | "publishConfig": { 49 | "access": "public" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rules/goldstein/test/fixture/convert-t-compile-to-compile-fix.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: import', async ({compile}) => { 2 | await compile('import'); 3 | }); 4 | -------------------------------------------------------------------------------- /rules/goldstein/test/fixture/convert-t-compile-to-compile.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: import', (t) => { 2 | t.compile('import'); 3 | t.end(); 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /rules/goldstein/test/fixture/convert-t-raise-to-raise-fix.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: try: not-supported', async ({raise}) => { 2 | await raise('not-supported', `After 'try' only '{', 'await' and 'function call' can come (1:8)`); 3 | }); 4 | -------------------------------------------------------------------------------- /rules/goldstein/test/fixture/convert-t-raise-to-raise.js: -------------------------------------------------------------------------------- 1 | test('goldstein: keyword: try: not-supported', (t) => { 2 | t.raise('not-supported', `After 'try' only '{', 'await' and 'function call' can come (1:8)`); 3 | }); 4 | -------------------------------------------------------------------------------- /rules/goldstein/test/goldstein.js: -------------------------------------------------------------------------------- 1 | import {createTest} from '@putout/test'; 2 | import * as goldstein from '../lib/index.js'; 3 | 4 | const test = createTest(import.meta.url, { 5 | printer: 'putout', 6 | plugins: [ 7 | ['goldstein', goldstein], 8 | ], 9 | }); 10 | 11 | test('plugin-goldstein: transform: convert-t-compile-to-compile', (t) => { 12 | t.transform('convert-t-compile-to-compile'); 13 | t.end(); 14 | }); 15 | 16 | test('plugin-goldstein: transform: convert-t-raise-to-raise', (t) => { 17 | t.transform('convert-t-raise-to-raise'); 18 | t.end(); 19 | }); 20 | --------------------------------------------------------------------------------