├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── script ├── gen-types.ts └── run-tests.sh ├── src ├── def │ ├── babel-core.ts │ ├── babel.ts │ ├── core.ts │ ├── es-proposals.ts │ ├── es2016.ts │ ├── es2017.ts │ ├── es2018.ts │ ├── es2019.ts │ ├── es2020.ts │ ├── es2021.ts │ ├── es2022.ts │ ├── es6.ts │ ├── esprima.ts │ ├── flow.ts │ ├── jsx.ts │ ├── operators │ │ ├── core.ts │ │ ├── es2016.ts │ │ ├── es2020.ts │ │ └── es2021.ts │ ├── type-annotations.ts │ └── typescript.ts ├── equiv.ts ├── esprima.d.ts ├── fork.ts ├── gen │ ├── builders.ts │ ├── kinds.ts │ ├── namedTypes.ts │ └── visitor.ts ├── main.ts ├── modules.d.ts ├── node-path.ts ├── path-visitor.ts ├── path.ts ├── scope.ts ├── shared.ts ├── test │ ├── api.ts │ ├── data │ │ ├── backbone.js │ │ └── jquery-1.9.1.js │ ├── ecmascript.ts │ ├── flow.ts │ ├── perf.ts │ ├── run.ts │ ├── shared.ts │ ├── type-annotations.ts │ └── typescript.ts └── types.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Windows Bash complains if the script contains \r characters 2 | *.sh text eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | node_version: ['12', '14', '16', '18', '19'] 16 | os: [ubuntu-latest] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node_version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node_version }} 24 | 25 | - name: npm install, build and test 26 | run: | 27 | npm install 28 | npm run build --if-present 29 | npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /lib 3 | /src/test/data/babel-parser 4 | /src/test/data/typescript-compiler 5 | 6 | # Ignore "npm pack" output 7 | /*.tgz 8 | 9 | # Ignore TypeScript-emitted files 10 | lib/ 11 | *.js 12 | *.d.ts 13 | # Except... 14 | !/types/**/*.d.ts 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything by default 2 | ** 3 | 4 | # Use negative patterns to bring back the specific things we want to publish 5 | !/lib/** 6 | 7 | # But exclude test folders 8 | /lib/**/test 9 | 10 | # NOTE: These don't need to be specified, because NPM includes them automatically. 11 | # 12 | # package.json 13 | # README (and its variants) 14 | # CHANGELOG (and its variants) 15 | # LICENSE / LICENCE 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Node.js inspector", 6 | "port": 9229, 7 | "request": "attach", 8 | "skipFiles": [ 9 | "/**" 10 | ], 11 | "type": "node" 12 | }, 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Ben Newman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Ben Newman ", 3 | "name": "ast-types", 4 | "version": "0.16.1", 5 | "description": "Esprima-compatible implementation of the Mozilla JS Parser API", 6 | "keywords": [ 7 | "ast", 8 | "abstract syntax tree", 9 | "hierarchy", 10 | "mozilla", 11 | "spidermonkey", 12 | "parser api", 13 | "esprima", 14 | "types", 15 | "type system", 16 | "type checking", 17 | "dynamic types", 18 | "parsing", 19 | "transformation", 20 | "syntax" 21 | ], 22 | "homepage": "http://github.com/benjamn/ast-types", 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/benjamn/ast-types.git" 26 | }, 27 | "license": "MIT", 28 | "main": "lib/main.js", 29 | "types": "lib/main.d.ts", 30 | "exports": { 31 | ".": "./lib/main.js", 32 | "./lib/*": "./lib/*.js", 33 | "./lib/*.js": "./lib/*.js", 34 | "./*": "./lib/*.js", 35 | "./*.js": "./lib/*.js" 36 | }, 37 | "scripts": { 38 | "gen": "ts-node --transpile-only script/gen-types.ts", 39 | "test": "npm run gen && npm run build && script/run-tests.sh", 40 | "clean": "rimraf lib/", 41 | "build": "tsc", 42 | "prepare": "npm run clean && npm run build" 43 | }, 44 | "dependencies": { 45 | "tslib": "^2.0.1" 46 | }, 47 | "devDependencies": { 48 | "@babel/parser": "7.20.5", 49 | "@babel/types": "7.20.5", 50 | "@types/esprima": "4.0.3", 51 | "@types/glob": "8.0.0", 52 | "@types/mocha": "10.0.1", 53 | "espree": "9.4.1", 54 | "esprima": "4.0.1", 55 | "esprima-fb": "15001.1001.0-dev-harmony-fb", 56 | "flow-parser": "0.195.2", 57 | "glob": "8.0.3", 58 | "mocha": "^10.2.0", 59 | "recast": "^0.23.0", 60 | "reify": "0.20.12", 61 | "rimraf": "3.0.2", 62 | "ts-node": "10.9.1", 63 | "typescript": "4.9.4" 64 | }, 65 | "engines": { 66 | "node": ">=4" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /script/gen-types.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { prettyPrint } from "recast"; 4 | import { 5 | Type, 6 | builders as b, 7 | namedTypes as n, 8 | getBuilderName, 9 | } from "../src/main"; 10 | 11 | const Op = Object.prototype; 12 | const hasOwn = Op.hasOwnProperty; 13 | 14 | const RESERVED_WORDS: { [reservedWord: string]: boolean | undefined } = { 15 | extends: true, 16 | default: true, 17 | arguments: true, 18 | static: true, 19 | }; 20 | 21 | const NAMED_TYPES_ID = b.identifier("namedTypes"); 22 | const NAMED_TYPES_IMPORT = b.importDeclaration( 23 | [b.importSpecifier(NAMED_TYPES_ID)], 24 | b.stringLiteral("./namedTypes"), 25 | ); 26 | 27 | const KINDS_ID = b.identifier("K"); 28 | const KINDS_IMPORT = b.importDeclaration( 29 | [b.importNamespaceSpecifier(KINDS_ID)], 30 | b.stringLiteral("./kinds") 31 | ); 32 | 33 | const supertypeToSubtypes = getSupertypeToSubtypes(); 34 | const builderTypeNames = getBuilderTypeNames(); 35 | 36 | const out = [ 37 | { 38 | file: "kinds.ts", 39 | ast: moduleWithBody([ 40 | NAMED_TYPES_IMPORT, 41 | ...Object.keys(supertypeToSubtypes).map(supertype => { 42 | const buildableSubtypes = getBuildableSubtypes(supertype); 43 | if (buildableSubtypes.length === 0) { 44 | // Some of the XML* types don't have buildable subtypes, 45 | // so fall back to using the supertype's node type 46 | return b.exportNamedDeclaration( 47 | b.tsTypeAliasDeclaration( 48 | b.identifier(`${supertype}Kind`), 49 | b.tsTypeReference(b.tsQualifiedName(NAMED_TYPES_ID, b.identifier(supertype))) 50 | ) 51 | ); 52 | } 53 | 54 | return b.exportNamedDeclaration( 55 | b.tsTypeAliasDeclaration( 56 | b.identifier(`${supertype}Kind`), 57 | b.tsUnionType(buildableSubtypes.map(subtype => 58 | b.tsTypeReference(b.tsQualifiedName(NAMED_TYPES_ID, b.identifier(subtype))) 59 | )) 60 | ) 61 | ); 62 | }), 63 | ]), 64 | }, 65 | { 66 | file: "namedTypes.ts", 67 | ast: moduleWithBody([ 68 | b.importDeclaration([ 69 | b.importSpecifier(b.identifier("Type")), 70 | b.importSpecifier(b.identifier("Omit")), 71 | ], b.stringLiteral("../types")), 72 | KINDS_IMPORT, 73 | b.exportNamedDeclaration( 74 | b.tsModuleDeclaration( 75 | b.identifier("namedTypes"), 76 | b.tsModuleBlock([ 77 | ...Object.keys(n).map(typeName => { 78 | const typeDef = Type.def(typeName); 79 | const ownFieldNames = Object.keys(typeDef.ownFields); 80 | 81 | return b.exportNamedDeclaration( 82 | b.tsInterfaceDeclaration.from({ 83 | id: b.identifier(typeName), 84 | extends: typeDef.baseNames.map(baseName => { 85 | const baseDef = Type.def(baseName); 86 | const commonFieldNames = ownFieldNames 87 | .filter(fieldName => !!baseDef.allFields[fieldName]); 88 | 89 | if (commonFieldNames.length > 0) { 90 | return b.tsExpressionWithTypeArguments( 91 | b.identifier("Omit"), 92 | b.tsTypeParameterInstantiation([ 93 | b.tsTypeReference(b.identifier(baseName)), 94 | b.tsUnionType( 95 | commonFieldNames.map(fieldName => 96 | b.tsLiteralType(b.stringLiteral(fieldName)) 97 | ) 98 | ), 99 | ]) 100 | ); 101 | } else { 102 | return b.tsExpressionWithTypeArguments(b.identifier(baseName)); 103 | } 104 | }), 105 | body: b.tsInterfaceBody( 106 | ownFieldNames.map(fieldName => { 107 | const field = typeDef.allFields[fieldName]; 108 | 109 | if (field.name === "type" && field.defaultFn) { 110 | return b.tsPropertySignature( 111 | b.identifier("type"), 112 | b.tsTypeAnnotation(b.tsLiteralType(b.stringLiteral(field.defaultFn()))) 113 | ); 114 | } else if (field.defaultFn) { 115 | return b.tsPropertySignature( 116 | b.identifier(field.name), 117 | b.tsTypeAnnotation(getTSTypeAnnotation(field.type)), 118 | true, // optional 119 | ); 120 | } 121 | 122 | return b.tsPropertySignature( 123 | b.identifier(field.name), 124 | b.tsTypeAnnotation(getTSTypeAnnotation(field.type)) 125 | ); 126 | }) 127 | ), 128 | }) 129 | ); 130 | }), 131 | 132 | b.exportNamedDeclaration( 133 | b.tsTypeAliasDeclaration( 134 | b.identifier("ASTNode"), 135 | b.tsUnionType( 136 | Object.keys(n) 137 | .filter(typeName => Type.def(typeName).buildable) 138 | .map(typeName => b.tsTypeReference(b.identifier(typeName))), 139 | ) 140 | ) 141 | ), 142 | 143 | ...Object.keys(n).map(typeName => 144 | b.exportNamedDeclaration( 145 | b.variableDeclaration("let", [ 146 | b.variableDeclarator( 147 | b.identifier.from({ 148 | name: typeName, 149 | typeAnnotation: b.tsTypeAnnotation( 150 | b.tsTypeReference( 151 | b.identifier("Type"), 152 | b.tsTypeParameterInstantiation([ 153 | b.tsTypeReference( 154 | b.identifier(typeName), 155 | ), 156 | ]), 157 | ), 158 | ), 159 | }), 160 | ), 161 | ]), 162 | ), 163 | ), 164 | ]), 165 | ) 166 | ), 167 | b.exportNamedDeclaration( 168 | b.tsInterfaceDeclaration( 169 | b.identifier("NamedTypes"), 170 | b.tsInterfaceBody( 171 | Object.keys(n).map(typeName => 172 | b.tsPropertySignature( 173 | b.identifier(typeName), 174 | b.tsTypeAnnotation( 175 | b.tsTypeReference( 176 | b.identifier("Type"), 177 | b.tsTypeParameterInstantiation([ 178 | b.tsTypeReference(b.tsQualifiedName( 179 | b.identifier("namedTypes"), 180 | b.identifier(typeName), 181 | )), 182 | ]) 183 | ) 184 | ) 185 | ) 186 | ) 187 | ) 188 | ) 189 | ), 190 | ]), 191 | }, 192 | { 193 | file: "builders.ts", 194 | ast: moduleWithBody([ 195 | KINDS_IMPORT, 196 | NAMED_TYPES_IMPORT, 197 | ...builderTypeNames.map(typeName => { 198 | const typeDef = Type.def(typeName); 199 | 200 | const returnType = b.tsTypeAnnotation( 201 | b.tsTypeReference(b.tsQualifiedName(NAMED_TYPES_ID, b.identifier(typeName))) 202 | ); 203 | 204 | const buildParamAllowsUndefined: { [buildParam: string]: boolean } = {}; 205 | const buildParamIsOptional: { [buildParam: string]: boolean } = {}; 206 | [...typeDef.buildParams].reverse().forEach((cur, i, arr) => { 207 | const field = typeDef.allFields[cur]; 208 | if (field && field.defaultFn) { 209 | if (i === 0) { 210 | buildParamIsOptional[cur] = true; 211 | } else { 212 | if (buildParamIsOptional[arr[i - 1]]) { 213 | buildParamIsOptional[cur] = true; 214 | } else { 215 | buildParamAllowsUndefined[cur] = true; 216 | } 217 | } 218 | } 219 | }); 220 | 221 | return b.exportNamedDeclaration( 222 | b.tsInterfaceDeclaration( 223 | b.identifier(`${typeName}Builder`), 224 | b.tsInterfaceBody([ 225 | b.tsCallSignatureDeclaration( 226 | typeDef.buildParams 227 | .filter(buildParam => !!typeDef.allFields[buildParam]) 228 | .map(buildParam => { 229 | const field = typeDef.allFields[buildParam]; 230 | const name = RESERVED_WORDS[buildParam] ? `${buildParam}Param` : buildParam; 231 | 232 | return b.identifier.from({ 233 | name, 234 | typeAnnotation: b.tsTypeAnnotation( 235 | !!buildParamAllowsUndefined[buildParam] 236 | ? b.tsUnionType([getTSTypeAnnotation(field.type), b.tsUndefinedKeyword()]) 237 | : getTSTypeAnnotation(field.type) 238 | ), 239 | optional: !!buildParamIsOptional[buildParam], 240 | }); 241 | }), 242 | returnType 243 | ), 244 | b.tsMethodSignature( 245 | b.identifier("from"), 246 | [ 247 | b.identifier.from({ 248 | name: "params", 249 | typeAnnotation: b.tsTypeAnnotation( 250 | b.tsTypeLiteral( 251 | Object.keys(typeDef.allFields) 252 | .filter(fieldName => fieldName !== "type") 253 | .sort() // Sort field name strings lexicographically. 254 | .map(fieldName => { 255 | const field = typeDef.allFields[fieldName]; 256 | return b.tsPropertySignature( 257 | b.identifier(field.name), 258 | b.tsTypeAnnotation(getTSTypeAnnotation(field.type)), 259 | field.defaultFn != null || field.hidden 260 | ); 261 | }) 262 | ) 263 | ), 264 | }), 265 | ], 266 | returnType 267 | ), 268 | ]) 269 | ) 270 | ); 271 | }), 272 | 273 | b.exportNamedDeclaration( 274 | b.tsInterfaceDeclaration( 275 | b.identifier("builders"), 276 | b.tsInterfaceBody([ 277 | ...builderTypeNames.map(typeName => 278 | b.tsPropertySignature( 279 | b.identifier(getBuilderName(typeName)), 280 | b.tsTypeAnnotation(b.tsTypeReference(b.identifier(`${typeName}Builder`))) 281 | ) 282 | ), 283 | b.tsIndexSignature( 284 | [ 285 | b.identifier.from({ 286 | name: "builderName", 287 | typeAnnotation: b.tsTypeAnnotation(b.tsStringKeyword()), 288 | }), 289 | ], 290 | b.tsTypeAnnotation(b.tsAnyKeyword()) 291 | ), 292 | ]) 293 | ) 294 | ), 295 | ]), 296 | }, 297 | { 298 | file: "visitor.ts", 299 | ast: moduleWithBody([ 300 | b.importDeclaration( 301 | [b.importSpecifier(b.identifier("NodePath"))], 302 | b.stringLiteral("../node-path") 303 | ), 304 | b.importDeclaration( 305 | [b.importSpecifier(b.identifier("Context"))], 306 | b.stringLiteral("../path-visitor") 307 | ), 308 | NAMED_TYPES_IMPORT, 309 | b.exportNamedDeclaration( 310 | b.tsInterfaceDeclaration.from({ 311 | id: b.identifier("Visitor"), 312 | typeParameters: b.tsTypeParameterDeclaration([ 313 | b.tsTypeParameter("M", undefined, b.tsTypeLiteral([])), 314 | ]), 315 | body: b.tsInterfaceBody([ 316 | ...Object.keys(n).map(typeName => { 317 | return b.tsMethodSignature.from({ 318 | key: b.identifier(`visit${typeName}`), 319 | parameters: [ 320 | b.identifier.from({ 321 | name: "this", 322 | typeAnnotation: b.tsTypeAnnotation( 323 | b.tsIntersectionType([ 324 | b.tsTypeReference(b.identifier("Context")), 325 | b.tsTypeReference(b.identifier("M")), 326 | ]) 327 | ), 328 | }), 329 | b.identifier.from({ 330 | name: "path", 331 | typeAnnotation: b.tsTypeAnnotation( 332 | b.tsTypeReference( 333 | b.identifier("NodePath"), 334 | b.tsTypeParameterInstantiation([ 335 | b.tsTypeReference(b.tsQualifiedName(NAMED_TYPES_ID, b.identifier(typeName))), 336 | ]) 337 | ) 338 | ), 339 | }), 340 | ], 341 | optional: true, 342 | typeAnnotation: b.tsTypeAnnotation(b.tsAnyKeyword()), 343 | }); 344 | }), 345 | ]), 346 | }) 347 | ), 348 | ]), 349 | }, 350 | ]; 351 | 352 | out.forEach(({ file, ast }) => { 353 | fs.writeFileSync( 354 | path.resolve(__dirname, `../src/gen/${file}`), 355 | prettyPrint(ast, { tabWidth: 2, includeComments: true }).code 356 | ); 357 | }); 358 | 359 | function moduleWithBody(body: any[]) { 360 | return b.file.from({ 361 | comments: [b.commentBlock(" !!! THIS FILE WAS AUTO-GENERATED BY `npm run gen` !!! ")], 362 | program: b.program(body), 363 | }); 364 | } 365 | 366 | function getSupertypeToSubtypes() { 367 | const supertypeToSubtypes: { [supertypeName: string]: string[] } = {}; 368 | Object.keys(n).map(typeName => { 369 | Type.def(typeName).supertypeList.forEach(supertypeName => { 370 | supertypeToSubtypes[supertypeName] = supertypeToSubtypes[supertypeName] || []; 371 | supertypeToSubtypes[supertypeName].push(typeName); 372 | }); 373 | }); 374 | 375 | return supertypeToSubtypes; 376 | } 377 | 378 | function getBuilderTypeNames() { 379 | return Object.keys(n).filter(typeName => { 380 | const typeDef = Type.def(typeName); 381 | const builderName = getBuilderName(typeName); 382 | 383 | return !!typeDef.buildParams && !!(b as any)[builderName]; 384 | }); 385 | } 386 | 387 | function getBuildableSubtypes(supertype: string): string[] { 388 | return Array.from(new Set( 389 | Object.keys(n).filter(typeName => { 390 | const typeDef = Type.def(typeName); 391 | return typeDef.allSupertypes[supertype] != null && typeDef.buildable; 392 | }) 393 | )); 394 | } 395 | 396 | function getTSTypeAnnotation(type: import("../src/types").Type): any { 397 | switch (type.kind) { 398 | case "ArrayType": { 399 | const elemTypeAnnotation = getTSTypeAnnotation(type.elemType); 400 | // TODO Improve this test. 401 | return n.TSUnionType.check(elemTypeAnnotation) 402 | ? b.tsArrayType(b.tsParenthesizedType(elemTypeAnnotation)) 403 | : b.tsArrayType(elemTypeAnnotation); 404 | } 405 | 406 | case "IdentityType": { 407 | if (type.value === null) { 408 | return b.tsNullKeyword(); 409 | } 410 | switch (typeof type.value) { 411 | case "undefined": 412 | return b.tsUndefinedKeyword(); 413 | case "string": 414 | return b.tsLiteralType(b.stringLiteral(type.value)); 415 | case "boolean": 416 | return b.tsLiteralType(b.booleanLiteral(type.value)); 417 | case "number": 418 | return b.tsNumberKeyword(); 419 | case "object": 420 | return b.tsObjectKeyword(); 421 | case "function": 422 | return b.tsFunctionType([]); 423 | case "symbol": 424 | return b.tsSymbolKeyword(); 425 | default: 426 | return b.tsAnyKeyword(); 427 | } 428 | } 429 | 430 | case "ObjectType": { 431 | return b.tsTypeLiteral( 432 | type.fields.map(field => 433 | b.tsPropertySignature( 434 | b.identifier(field.name), 435 | b.tsTypeAnnotation(getTSTypeAnnotation(field.type)) 436 | ) 437 | ) 438 | ); 439 | } 440 | 441 | case "OrType": { 442 | return b.tsUnionType(type.types.map(type => getTSTypeAnnotation(type))); 443 | } 444 | 445 | case "PredicateType": { 446 | if (typeof type.name !== "string") { 447 | return b.tsAnyKeyword(); 448 | } 449 | 450 | if (hasOwn.call(n, type.name)) { 451 | return b.tsTypeReference(b.tsQualifiedName(KINDS_ID, b.identifier(`${type.name}Kind`))); 452 | } 453 | 454 | if (/^[$A-Z_][a-z0-9_$]*$/i.test(type.name)) { 455 | return b.tsTypeReference(b.identifier(type.name)); 456 | } 457 | 458 | if (/^number [<>=]+ \d+$/.test(type.name)) { 459 | return b.tsNumberKeyword(); 460 | } 461 | 462 | // Not much else to do... 463 | return b.tsAnyKeyword(); 464 | } 465 | 466 | default: 467 | return assertNever(type); 468 | } 469 | } 470 | 471 | function assertNever(x: never): never { 472 | throw new Error("Unexpected: " + x); 473 | } 474 | -------------------------------------------------------------------------------- /script/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | cd "$(dirname $0)/../src/test/data" 6 | 7 | BAB_TAG=v$(node -p 'require("@babel/parser/package.json").version') 8 | 9 | if [ ! -d babel-parser ] 10 | then 11 | git clone --branch "$BAB_TAG" --depth 1 \ 12 | https://github.com/babel/babel.git 13 | mv babel/packages/babel-parser . 14 | rm -rf babel 15 | fi 16 | 17 | TS_TAG=v$(node -p 'require("typescript/package.json").version') 18 | 19 | if [ ! -d typescript-compiler ] 20 | then 21 | git clone --branch "$TS_TAG" --depth 1 \ 22 | https://github.com/Microsoft/TypeScript.git 23 | mv TypeScript/src/compiler typescript-compiler 24 | rm -rf TypeScript 25 | fi 26 | 27 | cd ../../.. # back to the ast-types/ root directory 28 | 29 | # Run Mocha on the generated .js code, rather than the .ts source code, so 30 | # that we're testing the same kind of output that we're shipping to npm. 31 | exec mocha --reporter spec --full-trace $@ lib/test/run.js 32 | -------------------------------------------------------------------------------- /src/def/babel-core.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import esProposalsDef from "./es-proposals"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | import { namedTypes as N } from "../gen/namedTypes"; 6 | 7 | export default function (fork: Fork) { 8 | fork.use(esProposalsDef); 9 | 10 | const types = fork.use(typesPlugin); 11 | const defaults = fork.use(sharedPlugin).defaults; 12 | const def = types.Type.def; 13 | const or = types.Type.or; 14 | const { 15 | undefined: isUndefined, 16 | } = types.builtInTypes; 17 | 18 | def("Noop") 19 | .bases("Statement") 20 | .build(); 21 | 22 | def("DoExpression") 23 | .bases("Expression") 24 | .build("body") 25 | .field("body", [def("Statement")]); 26 | 27 | def("BindExpression") 28 | .bases("Expression") 29 | .build("object", "callee") 30 | .field("object", or(def("Expression"), null)) 31 | .field("callee", def("Expression")); 32 | 33 | def("ParenthesizedExpression") 34 | .bases("Expression") 35 | .build("expression") 36 | .field("expression", def("Expression")); 37 | 38 | def("ExportNamespaceSpecifier") 39 | .bases("Specifier") 40 | .build("exported") 41 | .field("exported", def("Identifier")); 42 | 43 | def("ExportDefaultSpecifier") 44 | .bases("Specifier") 45 | .build("exported") 46 | .field("exported", def("Identifier")); 47 | 48 | def("CommentBlock") 49 | .bases("Comment") 50 | .build("value", /*optional:*/ "leading", "trailing"); 51 | 52 | def("CommentLine") 53 | .bases("Comment") 54 | .build("value", /*optional:*/ "leading", "trailing"); 55 | 56 | def("Directive") 57 | .bases("Node") 58 | .build("value") 59 | .field("value", def("DirectiveLiteral")); 60 | 61 | def("DirectiveLiteral") 62 | .bases("Node", "Expression") 63 | .build("value") 64 | .field("value", String, defaults["use strict"]); 65 | 66 | def("InterpreterDirective") 67 | .bases("Node") 68 | .build("value") 69 | .field("value", String); 70 | 71 | def("BlockStatement") 72 | .bases("Statement") 73 | .build("body") 74 | .field("body", [def("Statement")]) 75 | .field("directives", [def("Directive")], defaults.emptyArray); 76 | 77 | def("Program") 78 | .bases("Node") 79 | .build("body") 80 | .field("body", [def("Statement")]) 81 | .field("directives", [def("Directive")], defaults.emptyArray) 82 | .field("interpreter", or(def("InterpreterDirective"), null), defaults["null"]); 83 | 84 | function makeLiteralExtra< 85 | // Allowing N.RegExpLiteral explicitly here is important because the 86 | // node.value field of RegExpLiteral nodes can be undefined, which is not 87 | // allowed for other Literal subtypes. 88 | TNode extends Omit | N.RegExpLiteral 89 | >( 90 | rawValueType: any = String, 91 | toRaw?: (value: any) => string, 92 | ): Parameters { 93 | return [ 94 | "extra", 95 | { 96 | rawValue: rawValueType, 97 | raw: String, 98 | }, 99 | function getDefault(this: TNode) { 100 | const value = types.getFieldValue(this, "value"); 101 | return { 102 | rawValue: value, 103 | raw: toRaw ? toRaw(value) : String(value), 104 | }; 105 | }, 106 | ]; 107 | } 108 | 109 | // Split Literal 110 | def("StringLiteral") 111 | .bases("Literal") 112 | .build("value") 113 | .field("value", String) 114 | .field(...makeLiteralExtra(String, val => JSON.stringify(val))); 115 | 116 | def("NumericLiteral") 117 | .bases("Literal") 118 | .build("value") 119 | .field("value", Number) 120 | .field("raw", or(String, null), defaults["null"]) 121 | .field(...makeLiteralExtra(Number)); 122 | 123 | def("BigIntLiteral") 124 | .bases("Literal") 125 | .build("value") 126 | // Only String really seems appropriate here, since BigInt values 127 | // often exceed the limits of JS numbers. 128 | .field("value", or(String, Number)) 129 | .field(...makeLiteralExtra(String, val => val + "n")); 130 | 131 | // https://github.com/tc39/proposal-decimal 132 | // https://github.com/babel/babel/pull/11640 133 | def("DecimalLiteral") 134 | .bases("Literal") 135 | .build("value") 136 | .field("value", String) 137 | .field(...makeLiteralExtra(String, val => val + "m")); 138 | 139 | def("NullLiteral") 140 | .bases("Literal") 141 | .build() 142 | .field("value", null, defaults["null"]); 143 | 144 | def("BooleanLiteral") 145 | .bases("Literal") 146 | .build("value") 147 | .field("value", Boolean); 148 | 149 | def("RegExpLiteral") 150 | .bases("Literal") 151 | .build("pattern", "flags") 152 | .field("pattern", String) 153 | .field("flags", String) 154 | .field("value", RegExp, function (this: N.RegExpLiteral) { 155 | return new RegExp(this.pattern, this.flags); 156 | }) 157 | .field(...makeLiteralExtra( 158 | or(RegExp, isUndefined), 159 | exp => `/${exp.pattern}/${exp.flags || ""}`, 160 | )) 161 | // I'm not sure why this field exists, but it's "specified" by estree: 162 | // https://github.com/estree/estree/blob/master/es5.md#regexpliteral 163 | .field("regex", { 164 | pattern: String, 165 | flags: String 166 | }, function (this: N.RegExpLiteral) { 167 | return { 168 | pattern: this.pattern, 169 | flags: this.flags, 170 | }; 171 | }); 172 | 173 | var ObjectExpressionProperty = or( 174 | def("Property"), 175 | def("ObjectMethod"), 176 | def("ObjectProperty"), 177 | def("SpreadProperty"), 178 | def("SpreadElement") 179 | ); 180 | 181 | // Split Property -> ObjectProperty and ObjectMethod 182 | def("ObjectExpression") 183 | .bases("Expression") 184 | .build("properties") 185 | .field("properties", [ObjectExpressionProperty]); 186 | 187 | // ObjectMethod hoist .value properties to own properties 188 | def("ObjectMethod") 189 | .bases("Node", "Function") 190 | .build("kind", "key", "params", "body", "computed") 191 | .field("kind", or("method", "get", "set")) 192 | .field("key", or(def("Literal"), def("Identifier"), def("Expression"))) 193 | .field("params", [def("Pattern")]) 194 | .field("body", def("BlockStatement")) 195 | .field("computed", Boolean, defaults["false"]) 196 | .field("generator", Boolean, defaults["false"]) 197 | .field("async", Boolean, defaults["false"]) 198 | .field("accessibility", // TypeScript 199 | or(def("Literal"), null), 200 | defaults["null"]) 201 | .field("decorators", 202 | or([def("Decorator")], null), 203 | defaults["null"]); 204 | 205 | def("ObjectProperty") 206 | .bases("Node") 207 | .build("key", "value") 208 | .field("key", or(def("Literal"), def("Identifier"), def("Expression"))) 209 | .field("value", or(def("Expression"), def("Pattern"))) 210 | .field("accessibility", // TypeScript 211 | or(def("Literal"), null), 212 | defaults["null"]) 213 | .field("computed", Boolean, defaults["false"]); 214 | 215 | var ClassBodyElement = or( 216 | def("MethodDefinition"), 217 | def("VariableDeclarator"), 218 | def("ClassPropertyDefinition"), 219 | def("ClassProperty"), 220 | def("ClassPrivateProperty"), 221 | def("ClassMethod"), 222 | def("ClassPrivateMethod"), 223 | def("ClassAccessorProperty"), 224 | def("StaticBlock"), 225 | ); 226 | 227 | // MethodDefinition -> ClassMethod 228 | def("ClassBody") 229 | .bases("Declaration") 230 | .build("body") 231 | .field("body", [ClassBodyElement]); 232 | 233 | def("ClassMethod") 234 | .bases("Declaration", "Function") 235 | .build("kind", "key", "params", "body", "computed", "static") 236 | .field("key", or(def("Literal"), def("Identifier"), def("Expression"))); 237 | 238 | def("ClassPrivateMethod") 239 | .bases("Declaration", "Function") 240 | .build("key", "params", "body", "kind", "computed", "static") 241 | .field("key", def("PrivateName")); 242 | 243 | def("ClassAccessorProperty") 244 | .bases("Declaration") 245 | .build("key", "value", "decorators", "computed", "static") 246 | .field("key", or( 247 | def("Literal"), 248 | def("Identifier"), 249 | def("PrivateName"), 250 | // Only when .computed is true (TODO enforce this) 251 | def("Expression"), 252 | )) 253 | .field("value", or(def("Expression"), null), defaults["null"]); 254 | 255 | ["ClassMethod", 256 | "ClassPrivateMethod", 257 | ].forEach(typeName => { 258 | def(typeName) 259 | .field("kind", or("get", "set", "method", "constructor"), () => "method") 260 | .field("body", def("BlockStatement")) 261 | // For backwards compatibility only. Expect accessibility instead (see below). 262 | .field("access", or("public", "private", "protected", null), defaults["null"]) 263 | }); 264 | 265 | ["ClassMethod", 266 | "ClassPrivateMethod", 267 | "ClassAccessorProperty", 268 | ].forEach(typeName => { 269 | def(typeName) 270 | .field("computed", Boolean, defaults["false"]) 271 | .field("static", Boolean, defaults["false"]) 272 | .field("abstract", Boolean, defaults["false"]) 273 | .field("accessibility", or("public", "private", "protected", null), defaults["null"]) 274 | .field("decorators", or([def("Decorator")], null), defaults["null"]) 275 | .field("definite", Boolean, defaults["false"]) 276 | .field("optional", Boolean, defaults["false"]) 277 | .field("override", Boolean, defaults["false"]) 278 | .field("readonly", Boolean, defaults["false"]); 279 | }); 280 | 281 | var ObjectPatternProperty = or( 282 | def("Property"), 283 | def("PropertyPattern"), 284 | def("SpreadPropertyPattern"), 285 | def("SpreadProperty"), // Used by Esprima 286 | def("ObjectProperty"), // Babel 6 287 | def("RestProperty"), // Babel 6 288 | def("RestElement"), // Babel 6 289 | ); 290 | 291 | // Split into RestProperty and SpreadProperty 292 | def("ObjectPattern") 293 | .bases("Pattern") 294 | .build("properties") 295 | .field("properties", [ObjectPatternProperty]) 296 | .field("decorators", 297 | or([def("Decorator")], null), 298 | defaults["null"]); 299 | 300 | def("SpreadProperty") 301 | .bases("Node") 302 | .build("argument") 303 | .field("argument", def("Expression")); 304 | 305 | def("RestProperty") 306 | .bases("Node") 307 | .build("argument") 308 | .field("argument", def("Expression")); 309 | 310 | def("ForAwaitStatement") 311 | .bases("Statement") 312 | .build("left", "right", "body") 313 | .field("left", or( 314 | def("VariableDeclaration"), 315 | def("Expression"))) 316 | .field("right", def("Expression")) 317 | .field("body", def("Statement")); 318 | 319 | // The callee node of a dynamic import(...) expression. 320 | def("Import") 321 | .bases("Expression") 322 | .build(); 323 | }; 324 | 325 | maybeSetModuleExports(() => module); 326 | -------------------------------------------------------------------------------- /src/def/babel.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import typesPlugin from "../types"; 3 | import babelCoreDef from "./babel-core"; 4 | import flowDef from "./flow"; 5 | import { maybeSetModuleExports } from "../shared"; 6 | 7 | export default function (fork: Fork) { 8 | const types = fork.use(typesPlugin); 9 | const def = types.Type.def; 10 | 11 | fork.use(babelCoreDef); 12 | fork.use(flowDef); 13 | 14 | // https://github.com/babel/babel/pull/10148 15 | def("V8IntrinsicIdentifier") 16 | .bases("Expression") 17 | .build("name") 18 | .field("name", String); 19 | 20 | // https://github.com/babel/babel/pull/13191 21 | // https://github.com/babel/website/pull/2541 22 | def("TopicReference") 23 | .bases("Expression") 24 | .build(); 25 | } 26 | 27 | maybeSetModuleExports(() => module); 28 | -------------------------------------------------------------------------------- /src/def/core.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import coreOpsDef from "./operators/core"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | import { namedTypes as N } from "../gen/namedTypes"; 6 | 7 | export default function (fork: Fork) { 8 | var types = fork.use(typesPlugin); 9 | var Type = types.Type; 10 | var def = Type.def; 11 | var or = Type.or; 12 | var shared = fork.use(sharedPlugin); 13 | var defaults = shared.defaults; 14 | var geq = shared.geq; 15 | 16 | const { 17 | BinaryOperators, 18 | AssignmentOperators, 19 | LogicalOperators, 20 | } = fork.use(coreOpsDef); 21 | 22 | // Abstract supertype of all syntactic entities that are allowed to have a 23 | // .loc field. 24 | def("Printable") 25 | .field("loc", or( 26 | def("SourceLocation"), 27 | null 28 | ), defaults["null"], true); 29 | 30 | def("Node") 31 | .bases("Printable") 32 | .field("type", String) 33 | .field("comments", or( 34 | [def("Comment")], 35 | null 36 | ), defaults["null"], true); 37 | 38 | def("SourceLocation") 39 | .field("start", def("Position")) 40 | .field("end", def("Position")) 41 | .field("source", or(String, null), defaults["null"]); 42 | 43 | def("Position") 44 | .field("line", geq(1)) 45 | .field("column", geq(0)); 46 | 47 | def("File") 48 | .bases("Node") 49 | .build("program", "name") 50 | .field("program", def("Program")) 51 | .field("name", or(String, null), defaults["null"]); 52 | 53 | def("Program") 54 | .bases("Node") 55 | .build("body") 56 | .field("body", [def("Statement")]); 57 | 58 | def("Function") 59 | .bases("Node") 60 | .field("id", or(def("Identifier"), null), defaults["null"]) 61 | .field("params", [def("Pattern")]) 62 | .field("body", def("BlockStatement")) 63 | .field("generator", Boolean, defaults["false"]) 64 | .field("async", Boolean, defaults["false"]); 65 | 66 | def("Statement").bases("Node"); 67 | 68 | // The empty .build() here means that an EmptyStatement can be constructed 69 | // (i.e. it's not abstract) but that it needs no arguments. 70 | def("EmptyStatement").bases("Statement").build(); 71 | 72 | def("BlockStatement") 73 | .bases("Statement") 74 | .build("body") 75 | .field("body", [def("Statement")]); 76 | 77 | // TODO Figure out how to silently coerce Expressions to 78 | // ExpressionStatements where a Statement was expected. 79 | def("ExpressionStatement") 80 | .bases("Statement") 81 | .build("expression") 82 | .field("expression", def("Expression")); 83 | 84 | def("IfStatement") 85 | .bases("Statement") 86 | .build("test", "consequent", "alternate") 87 | .field("test", def("Expression")) 88 | .field("consequent", def("Statement")) 89 | .field("alternate", or(def("Statement"), null), defaults["null"]); 90 | 91 | def("LabeledStatement") 92 | .bases("Statement") 93 | .build("label", "body") 94 | .field("label", def("Identifier")) 95 | .field("body", def("Statement")); 96 | 97 | def("BreakStatement") 98 | .bases("Statement") 99 | .build("label") 100 | .field("label", or(def("Identifier"), null), defaults["null"]); 101 | 102 | def("ContinueStatement") 103 | .bases("Statement") 104 | .build("label") 105 | .field("label", or(def("Identifier"), null), defaults["null"]); 106 | 107 | def("WithStatement") 108 | .bases("Statement") 109 | .build("object", "body") 110 | .field("object", def("Expression")) 111 | .field("body", def("Statement")); 112 | 113 | def("SwitchStatement") 114 | .bases("Statement") 115 | .build("discriminant", "cases", "lexical") 116 | .field("discriminant", def("Expression")) 117 | .field("cases", [def("SwitchCase")]) 118 | .field("lexical", Boolean, defaults["false"]); 119 | 120 | def("ReturnStatement") 121 | .bases("Statement") 122 | .build("argument") 123 | .field("argument", or(def("Expression"), null)); 124 | 125 | def("ThrowStatement") 126 | .bases("Statement") 127 | .build("argument") 128 | .field("argument", def("Expression")); 129 | 130 | def("TryStatement") 131 | .bases("Statement") 132 | .build("block", "handler", "finalizer") 133 | .field("block", def("BlockStatement")) 134 | .field("handler", or(def("CatchClause"), null), function (this: N.TryStatement) { 135 | return this.handlers && this.handlers[0] || null; 136 | }) 137 | .field("handlers", [def("CatchClause")], function (this: N.TryStatement) { 138 | return this.handler ? [this.handler] : []; 139 | }, true) // Indicates this field is hidden from eachField iteration. 140 | .field("guardedHandlers", [def("CatchClause")], defaults.emptyArray) 141 | .field("finalizer", or(def("BlockStatement"), null), defaults["null"]); 142 | 143 | def("CatchClause") 144 | .bases("Node") 145 | .build("param", "guard", "body") 146 | .field("param", def("Pattern")) 147 | .field("guard", or(def("Expression"), null), defaults["null"]) 148 | .field("body", def("BlockStatement")); 149 | 150 | def("WhileStatement") 151 | .bases("Statement") 152 | .build("test", "body") 153 | .field("test", def("Expression")) 154 | .field("body", def("Statement")); 155 | 156 | def("DoWhileStatement") 157 | .bases("Statement") 158 | .build("body", "test") 159 | .field("body", def("Statement")) 160 | .field("test", def("Expression")); 161 | 162 | def("ForStatement") 163 | .bases("Statement") 164 | .build("init", "test", "update", "body") 165 | .field("init", or( 166 | def("VariableDeclaration"), 167 | def("Expression"), 168 | null)) 169 | .field("test", or(def("Expression"), null)) 170 | .field("update", or(def("Expression"), null)) 171 | .field("body", def("Statement")); 172 | 173 | def("ForInStatement") 174 | .bases("Statement") 175 | .build("left", "right", "body") 176 | .field("left", or( 177 | def("VariableDeclaration"), 178 | def("Expression"))) 179 | .field("right", def("Expression")) 180 | .field("body", def("Statement")); 181 | 182 | def("DebuggerStatement").bases("Statement").build(); 183 | 184 | def("Declaration").bases("Statement"); 185 | 186 | def("FunctionDeclaration") 187 | .bases("Function", "Declaration") 188 | .build("id", "params", "body") 189 | .field("id", def("Identifier")); 190 | 191 | def("FunctionExpression") 192 | .bases("Function", "Expression") 193 | .build("id", "params", "body"); 194 | 195 | def("VariableDeclaration") 196 | .bases("Declaration") 197 | .build("kind", "declarations") 198 | .field("kind", or("var", "let", "const")) 199 | .field("declarations", [def("VariableDeclarator")]); 200 | 201 | def("VariableDeclarator") 202 | .bases("Node") 203 | .build("id", "init") 204 | .field("id", def("Pattern")) 205 | .field("init", or(def("Expression"), null), defaults["null"]); 206 | 207 | def("Expression").bases("Node"); 208 | 209 | def("ThisExpression").bases("Expression").build(); 210 | 211 | def("ArrayExpression") 212 | .bases("Expression") 213 | .build("elements") 214 | .field("elements", [or(def("Expression"), null)]); 215 | 216 | def("ObjectExpression") 217 | .bases("Expression") 218 | .build("properties") 219 | .field("properties", [def("Property")]); 220 | 221 | // TODO Not in the Mozilla Parser API, but used by Esprima. 222 | def("Property") 223 | .bases("Node") // Want to be able to visit Property Nodes. 224 | .build("kind", "key", "value") 225 | .field("kind", or("init", "get", "set")) 226 | .field("key", or(def("Literal"), def("Identifier"))) 227 | .field("value", def("Expression")); 228 | 229 | def("SequenceExpression") 230 | .bases("Expression") 231 | .build("expressions") 232 | .field("expressions", [def("Expression")]); 233 | 234 | var UnaryOperator = or( 235 | "-", "+", "!", "~", 236 | "typeof", "void", "delete"); 237 | 238 | def("UnaryExpression") 239 | .bases("Expression") 240 | .build("operator", "argument", "prefix") 241 | .field("operator", UnaryOperator) 242 | .field("argument", def("Expression")) 243 | // Esprima doesn't bother with this field, presumably because it's 244 | // always true for unary operators. 245 | .field("prefix", Boolean, defaults["true"]); 246 | 247 | const BinaryOperator = or(...BinaryOperators); 248 | 249 | def("BinaryExpression") 250 | .bases("Expression") 251 | .build("operator", "left", "right") 252 | .field("operator", BinaryOperator) 253 | .field("left", def("Expression")) 254 | .field("right", def("Expression")); 255 | 256 | const AssignmentOperator = or(...AssignmentOperators); 257 | 258 | def("AssignmentExpression") 259 | .bases("Expression") 260 | .build("operator", "left", "right") 261 | .field("operator", AssignmentOperator) 262 | .field("left", or(def("Pattern"), def("MemberExpression"))) 263 | .field("right", def("Expression")); 264 | 265 | var UpdateOperator = or("++", "--"); 266 | 267 | def("UpdateExpression") 268 | .bases("Expression") 269 | .build("operator", "argument", "prefix") 270 | .field("operator", UpdateOperator) 271 | .field("argument", def("Expression")) 272 | .field("prefix", Boolean); 273 | 274 | var LogicalOperator = or(...LogicalOperators); 275 | 276 | def("LogicalExpression") 277 | .bases("Expression") 278 | .build("operator", "left", "right") 279 | .field("operator", LogicalOperator) 280 | .field("left", def("Expression")) 281 | .field("right", def("Expression")); 282 | 283 | def("ConditionalExpression") 284 | .bases("Expression") 285 | .build("test", "consequent", "alternate") 286 | .field("test", def("Expression")) 287 | .field("consequent", def("Expression")) 288 | .field("alternate", def("Expression")); 289 | 290 | def("NewExpression") 291 | .bases("Expression") 292 | .build("callee", "arguments") 293 | .field("callee", def("Expression")) 294 | // The Mozilla Parser API gives this type as [or(def("Expression"), 295 | // null)], but null values don't really make sense at the call site. 296 | // TODO Report this nonsense. 297 | .field("arguments", [def("Expression")]); 298 | 299 | def("CallExpression") 300 | .bases("Expression") 301 | .build("callee", "arguments") 302 | .field("callee", def("Expression")) 303 | // See comment for NewExpression above. 304 | .field("arguments", [def("Expression")]); 305 | 306 | def("MemberExpression") 307 | .bases("Expression") 308 | .build("object", "property", "computed") 309 | .field("object", def("Expression")) 310 | .field("property", or(def("Identifier"), def("Expression"))) 311 | .field("computed", Boolean, function (this: N.MemberExpression) { 312 | var type = this.property.type; 313 | if (type === 'Literal' || 314 | type === 'MemberExpression' || 315 | type === 'BinaryExpression') { 316 | return true; 317 | } 318 | return false; 319 | }); 320 | 321 | def("Pattern").bases("Node"); 322 | 323 | def("SwitchCase") 324 | .bases("Node") 325 | .build("test", "consequent") 326 | .field("test", or(def("Expression"), null)) 327 | .field("consequent", [def("Statement")]); 328 | 329 | def("Identifier") 330 | .bases("Expression", "Pattern") 331 | .build("name") 332 | .field("name", String) 333 | .field("optional", Boolean, defaults["false"]); 334 | 335 | def("Literal") 336 | .bases("Expression") 337 | .build("value") 338 | .field("value", or(String, Boolean, null, Number, RegExp, BigInt)); 339 | 340 | // Abstract (non-buildable) comment supertype. Not a Node. 341 | def("Comment") 342 | .bases("Printable") 343 | .field("value", String) 344 | // A .leading comment comes before the node, whereas a .trailing 345 | // comment comes after it. These two fields should not both be true, 346 | // but they might both be false when the comment falls inside a node 347 | // and the node has no children for the comment to lead or trail, 348 | // e.g. { /*dangling*/ }. 349 | .field("leading", Boolean, defaults["true"]) 350 | .field("trailing", Boolean, defaults["false"]); 351 | }; 352 | 353 | maybeSetModuleExports(() => module); 354 | -------------------------------------------------------------------------------- /src/def/es-proposals.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import typesPlugin from "../types"; 3 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 4 | import es2022Def from "./es2022"; 5 | 6 | export default function (fork: Fork) { 7 | fork.use(es2022Def); 8 | 9 | const types = fork.use(typesPlugin); 10 | const Type = types.Type; 11 | const def = types.Type.def; 12 | const or = Type.or; 13 | 14 | const shared = fork.use(sharedPlugin); 15 | const defaults = shared.defaults; 16 | 17 | def("AwaitExpression") 18 | .build("argument", "all") 19 | .field("argument", or(def("Expression"), null)) 20 | .field("all", Boolean, defaults["false"]); 21 | 22 | // Decorators 23 | def("Decorator") 24 | .bases("Node") 25 | .build("expression") 26 | .field("expression", def("Expression")); 27 | 28 | def("Property") 29 | .field("decorators", 30 | or([def("Decorator")], null), 31 | defaults["null"]); 32 | 33 | def("MethodDefinition") 34 | .field("decorators", 35 | or([def("Decorator")], null), 36 | defaults["null"]); 37 | 38 | // Private names 39 | def("PrivateName") 40 | .bases("Expression", "Pattern") 41 | .build("id") 42 | .field("id", def("Identifier")); 43 | 44 | def("ClassPrivateProperty") 45 | .bases("ClassProperty") 46 | .build("key", "value") 47 | .field("key", def("PrivateName")) 48 | .field("value", or(def("Expression"), null), defaults["null"]); 49 | 50 | // https://github.com/tc39/proposal-import-assertions 51 | def("ImportAttribute") 52 | .bases("Node") 53 | .build("key", "value") 54 | .field("key", or(def("Identifier"), def("Literal"))) 55 | .field("value", def("Expression")); 56 | 57 | [ "ImportDeclaration", 58 | "ExportAllDeclaration", 59 | "ExportNamedDeclaration", 60 | ].forEach(decl => { 61 | def(decl).field( 62 | "assertions", 63 | [def("ImportAttribute")], 64 | defaults.emptyArray, 65 | ); 66 | }); 67 | 68 | // https://github.com/tc39/proposal-record-tuple 69 | // https://github.com/babel/babel/pull/10865 70 | def("RecordExpression") 71 | .bases("Expression") 72 | .build("properties") 73 | .field("properties", [or( 74 | def("ObjectProperty"), 75 | def("ObjectMethod"), 76 | def("SpreadElement"), 77 | )]); 78 | def("TupleExpression") 79 | .bases("Expression") 80 | .build("elements") 81 | .field("elements", [or( 82 | def("Expression"), 83 | def("SpreadElement"), 84 | null, 85 | )]); 86 | 87 | // https://github.com/tc39/proposal-js-module-blocks 88 | // https://github.com/babel/babel/pull/12469 89 | def("ModuleExpression") 90 | .bases("Node") 91 | .build("body") 92 | .field("body", def("Program")); 93 | }; 94 | 95 | maybeSetModuleExports(() => module); 96 | -------------------------------------------------------------------------------- /src/def/es2016.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import es2016OpsDef from "./operators/es2016"; 3 | import es6Def from "./es6"; 4 | import { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | // The es2016OpsDef plugin comes before es6Def so BinaryOperators and 8 | // AssignmentOperators will be appropriately augmented before they are first 9 | // used in the core definitions for this fork. 10 | fork.use(es2016OpsDef); 11 | fork.use(es6Def); 12 | }; 13 | 14 | maybeSetModuleExports(() => module); 15 | -------------------------------------------------------------------------------- /src/def/es2017.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import es2016Def from "./es2016"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | fork.use(es2016Def); 8 | 9 | const types = fork.use(typesPlugin); 10 | const def = types.Type.def; 11 | const defaults = fork.use(sharedPlugin).defaults; 12 | 13 | def("Function") 14 | .field("async", Boolean, defaults["false"]); 15 | 16 | def("AwaitExpression") 17 | .bases("Expression") 18 | .build("argument") 19 | .field("argument", def("Expression")); 20 | }; 21 | 22 | maybeSetModuleExports(() => module); 23 | -------------------------------------------------------------------------------- /src/def/es2018.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import es2017Def from "./es2017"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | fork.use(es2017Def); 8 | 9 | const types = fork.use(typesPlugin); 10 | const def = types.Type.def; 11 | const or = types.Type.or; 12 | const defaults = fork.use(sharedPlugin).defaults; 13 | 14 | def("ForOfStatement") 15 | .field("await", Boolean, defaults["false"]); 16 | 17 | // Legacy 18 | def("SpreadProperty") 19 | .bases("Node") 20 | .build("argument") 21 | .field("argument", def("Expression")); 22 | 23 | def("ObjectExpression") 24 | .field("properties", [or( 25 | def("Property"), 26 | def("SpreadProperty"), // Legacy 27 | def("SpreadElement") 28 | )]); 29 | 30 | def("TemplateElement") 31 | .field("value", {"cooked": or(String, null), "raw": String}); 32 | 33 | // Legacy 34 | def("SpreadPropertyPattern") 35 | .bases("Pattern") 36 | .build("argument") 37 | .field("argument", def("Pattern")); 38 | 39 | def("ObjectPattern") 40 | .field("properties", [or(def("PropertyPattern"), def("Property"), def("RestElement"), def("SpreadPropertyPattern"))]); 41 | }; 42 | 43 | maybeSetModuleExports(() => module); 44 | -------------------------------------------------------------------------------- /src/def/es2019.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import es2018Def from "./es2018"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | fork.use(es2018Def); 8 | 9 | const types = fork.use(typesPlugin); 10 | const def = types.Type.def; 11 | const or = types.Type.or; 12 | const defaults = fork.use(sharedPlugin).defaults; 13 | 14 | def("CatchClause") 15 | .field("param", or(def("Pattern"), null), defaults["null"]); 16 | }; 17 | 18 | maybeSetModuleExports(() => module); 19 | -------------------------------------------------------------------------------- /src/def/es2020.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import es2020OpsDef from "./operators/es2020"; 3 | import es2019Def from "./es2019"; 4 | import typesPlugin from "../types"; 5 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 6 | 7 | export default function (fork: Fork) { 8 | // The es2020OpsDef plugin comes before es2019Def so LogicalOperators will be 9 | // appropriately augmented before first used. 10 | fork.use(es2020OpsDef); 11 | 12 | fork.use(es2019Def); 13 | 14 | const types = fork.use(typesPlugin); 15 | const def = types.Type.def; 16 | const or = types.Type.or; 17 | 18 | const shared = fork.use(sharedPlugin); 19 | const defaults = shared.defaults; 20 | 21 | def("ImportExpression") 22 | .bases("Expression") 23 | .build("source") 24 | .field("source", def("Expression")); 25 | 26 | def("ExportAllDeclaration") 27 | .bases("Declaration") 28 | .build("source", "exported") 29 | .field("source", def("Literal")) 30 | .field("exported", or( 31 | def("Identifier"), 32 | null, 33 | void 0, 34 | ), defaults["null"]); 35 | 36 | // Optional chaining 37 | def("ChainElement") 38 | .bases("Node") 39 | .field("optional", Boolean, defaults["false"]); 40 | 41 | def("CallExpression") 42 | .bases("Expression", "ChainElement"); 43 | 44 | def("MemberExpression") 45 | .bases("Expression", "ChainElement"); 46 | 47 | def("ChainExpression") 48 | .bases("Expression") 49 | .build("expression") 50 | .field("expression", def("ChainElement")); 51 | 52 | def("OptionalCallExpression") 53 | .bases("CallExpression") 54 | .build("callee", "arguments", "optional") 55 | .field("optional", Boolean, defaults["true"]); 56 | 57 | // Deprecated optional chaining type, doesn't work with babelParser@7.11.0 or newer 58 | def("OptionalMemberExpression") 59 | .bases("MemberExpression") 60 | .build("object", "property", "computed", "optional") 61 | .field("optional", Boolean, defaults["true"]); 62 | }; 63 | 64 | maybeSetModuleExports(() => module); 65 | -------------------------------------------------------------------------------- /src/def/es2021.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import es2021OpsDef from "./operators/es2021"; 3 | import es2020Def from "./es2020"; 4 | import { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | // The es2021OpsDef plugin comes before es2020Def so AssignmentOperators will 8 | // be appropriately augmented before first used. 9 | fork.use(es2021OpsDef); 10 | fork.use(es2020Def); 11 | } 12 | 13 | maybeSetModuleExports(() => module); 14 | -------------------------------------------------------------------------------- /src/def/es2022.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import es2021Def from "./es2021"; 3 | import typesPlugin from "../types"; 4 | import { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | fork.use(es2021Def); 8 | 9 | const types = fork.use(typesPlugin); 10 | const def = types.Type.def; 11 | 12 | def("StaticBlock") 13 | .bases("Declaration") 14 | .build("body") 15 | .field("body", [def("Statement")]); 16 | } 17 | 18 | maybeSetModuleExports(() => module); 19 | -------------------------------------------------------------------------------- /src/def/es6.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import coreDef from "./core"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | fork.use(coreDef); 8 | 9 | const types = fork.use(typesPlugin); 10 | const def = types.Type.def; 11 | const or = types.Type.or; 12 | const defaults = fork.use(sharedPlugin).defaults; 13 | 14 | def("Function") 15 | .field("generator", Boolean, defaults["false"]) 16 | .field("expression", Boolean, defaults["false"]) 17 | .field("defaults", [or(def("Expression"), null)], defaults.emptyArray) 18 | // Legacy 19 | .field("rest", or(def("Identifier"), null), defaults["null"]); 20 | 21 | // The ESTree way of representing a ...rest parameter. 22 | def("RestElement") 23 | .bases("Pattern") 24 | .build("argument") 25 | .field("argument", def("Pattern")) 26 | .field("typeAnnotation", // for Babylon. Flow parser puts it on the identifier 27 | or(def("TypeAnnotation"), def("TSTypeAnnotation"), null), defaults["null"]); 28 | 29 | def("SpreadElementPattern") 30 | .bases("Pattern") 31 | .build("argument") 32 | .field("argument", def("Pattern")); 33 | 34 | def("FunctionDeclaration") 35 | .build("id", "params", "body", "generator", "expression") 36 | // May be `null` in the context of `export default function () {}` 37 | .field("id", or(def("Identifier"), null)) 38 | 39 | def("FunctionExpression") 40 | .build("id", "params", "body", "generator", "expression"); 41 | 42 | def("ArrowFunctionExpression") 43 | .bases("Function", "Expression") 44 | .build("params", "body", "expression") 45 | // The forced null value here is compatible with the overridden 46 | // definition of the "id" field in the Function interface. 47 | .field("id", null, defaults["null"]) 48 | // Arrow function bodies are allowed to be expressions. 49 | .field("body", or(def("BlockStatement"), def("Expression"))) 50 | // The current spec forbids arrow generators, so I have taken the 51 | // liberty of enforcing that. TODO Report this. 52 | .field("generator", false, defaults["false"]); 53 | 54 | def("ForOfStatement") 55 | .bases("Statement") 56 | .build("left", "right", "body") 57 | .field("left", or( 58 | def("VariableDeclaration"), 59 | def("Pattern"))) 60 | .field("right", def("Expression")) 61 | .field("body", def("Statement")); 62 | 63 | def("YieldExpression") 64 | .bases("Expression") 65 | .build("argument", "delegate") 66 | .field("argument", or(def("Expression"), null)) 67 | .field("delegate", Boolean, defaults["false"]); 68 | 69 | def("GeneratorExpression") 70 | .bases("Expression") 71 | .build("body", "blocks", "filter") 72 | .field("body", def("Expression")) 73 | .field("blocks", [def("ComprehensionBlock")]) 74 | .field("filter", or(def("Expression"), null)); 75 | 76 | def("ComprehensionExpression") 77 | .bases("Expression") 78 | .build("body", "blocks", "filter") 79 | .field("body", def("Expression")) 80 | .field("blocks", [def("ComprehensionBlock")]) 81 | .field("filter", or(def("Expression"), null)); 82 | 83 | def("ComprehensionBlock") 84 | .bases("Node") 85 | .build("left", "right", "each") 86 | .field("left", def("Pattern")) 87 | .field("right", def("Expression")) 88 | .field("each", Boolean); 89 | 90 | def("Property") 91 | .field("key", or(def("Literal"), def("Identifier"), def("Expression"))) 92 | .field("value", or(def("Expression"), def("Pattern"))) 93 | .field("method", Boolean, defaults["false"]) 94 | .field("shorthand", Boolean, defaults["false"]) 95 | .field("computed", Boolean, defaults["false"]); 96 | 97 | def("ObjectProperty") 98 | .field("shorthand", Boolean, defaults["false"]); 99 | 100 | def("PropertyPattern") 101 | .bases("Pattern") 102 | .build("key", "pattern") 103 | .field("key", or(def("Literal"), def("Identifier"), def("Expression"))) 104 | .field("pattern", def("Pattern")) 105 | .field("computed", Boolean, defaults["false"]); 106 | 107 | def("ObjectPattern") 108 | .bases("Pattern") 109 | .build("properties") 110 | .field("properties", [or(def("PropertyPattern"), def("Property"))]); 111 | 112 | def("ArrayPattern") 113 | .bases("Pattern") 114 | .build("elements") 115 | .field("elements", [or(def("Pattern"), null)]); 116 | 117 | def("SpreadElement") 118 | .bases("Node") 119 | .build("argument") 120 | .field("argument", def("Expression")); 121 | 122 | def("ArrayExpression") 123 | .field("elements", [or( 124 | def("Expression"), 125 | def("SpreadElement"), 126 | def("RestElement"), 127 | null 128 | )]); 129 | 130 | def("NewExpression") 131 | .field("arguments", [or(def("Expression"), def("SpreadElement"))]); 132 | 133 | def("CallExpression") 134 | .field("arguments", [or(def("Expression"), def("SpreadElement"))]); 135 | 136 | // Note: this node type is *not* an AssignmentExpression with a Pattern on 137 | // the left-hand side! The existing AssignmentExpression type already 138 | // supports destructuring assignments. AssignmentPattern nodes may appear 139 | // wherever a Pattern is allowed, and the right-hand side represents a 140 | // default value to be destructured against the left-hand side, if no 141 | // value is otherwise provided. For example: default parameter values. 142 | def("AssignmentPattern") 143 | .bases("Pattern") 144 | .build("left", "right") 145 | .field("left", def("Pattern")) 146 | .field("right", def("Expression")); 147 | 148 | def("MethodDefinition") 149 | .bases("Declaration") 150 | .build("kind", "key", "value", "static") 151 | .field("kind", or("constructor", "method", "get", "set")) 152 | .field("key", def("Expression")) 153 | .field("value", def("Function")) 154 | .field("computed", Boolean, defaults["false"]) 155 | .field("static", Boolean, defaults["false"]); 156 | 157 | const ClassBodyElement = or( 158 | def("MethodDefinition"), 159 | def("VariableDeclarator"), 160 | def("ClassPropertyDefinition"), 161 | def("ClassProperty"), 162 | def("StaticBlock"), 163 | ); 164 | 165 | def("ClassProperty") 166 | .bases("Declaration") 167 | .build("key") 168 | .field("key", or(def("Literal"), def("Identifier"), def("Expression"))) 169 | .field("computed", Boolean, defaults["false"]); 170 | 171 | def("ClassPropertyDefinition") // static property 172 | .bases("Declaration") 173 | .build("definition") 174 | // Yes, Virginia, circular definitions are permitted. 175 | .field("definition", ClassBodyElement); 176 | 177 | def("ClassBody") 178 | .bases("Declaration") 179 | .build("body") 180 | .field("body", [ClassBodyElement]); 181 | 182 | def("ClassDeclaration") 183 | .bases("Declaration") 184 | .build("id", "body", "superClass") 185 | .field("id", or(def("Identifier"), null)) 186 | .field("body", def("ClassBody")) 187 | .field("superClass", or(def("Expression"), null), defaults["null"]); 188 | 189 | def("ClassExpression") 190 | .bases("Expression") 191 | .build("id", "body", "superClass") 192 | .field("id", or(def("Identifier"), null), defaults["null"]) 193 | .field("body", def("ClassBody")) 194 | .field("superClass", or(def("Expression"), null), defaults["null"]); 195 | 196 | def("Super") 197 | .bases("Expression") 198 | .build(); 199 | 200 | // Specifier and ModuleSpecifier are abstract non-standard types 201 | // introduced for definitional convenience. 202 | def("Specifier").bases("Node"); 203 | 204 | // This supertype is shared/abused by both def/babel.js and 205 | // def/esprima.js. In the future, it will be possible to load only one set 206 | // of definitions appropriate for a given parser, but until then we must 207 | // rely on default functions to reconcile the conflicting AST formats. 208 | def("ModuleSpecifier") 209 | .bases("Specifier") 210 | // This local field is used by Babel/Acorn. It should not technically 211 | // be optional in the Babel/Acorn AST format, but it must be optional 212 | // in the Esprima AST format. 213 | .field("local", or(def("Identifier"), null), defaults["null"]) 214 | // The id and name fields are used by Esprima. The id field should not 215 | // technically be optional in the Esprima AST format, but it must be 216 | // optional in the Babel/Acorn AST format. 217 | .field("id", or(def("Identifier"), null), defaults["null"]) 218 | .field("name", or(def("Identifier"), null), defaults["null"]); 219 | 220 | // import {} from ...; 221 | def("ImportSpecifier") 222 | .bases("ModuleSpecifier") 223 | .build("imported", "local") 224 | .field("imported", def("Identifier")); 225 | 226 | // import from ...; 227 | def("ImportDefaultSpecifier") 228 | .bases("ModuleSpecifier") 229 | .build("local"); 230 | 231 | // import <* as id> from ...; 232 | def("ImportNamespaceSpecifier") 233 | .bases("ModuleSpecifier") 234 | .build("local"); 235 | 236 | def("ImportDeclaration") 237 | .bases("Declaration") 238 | .build("specifiers", "source", "importKind") 239 | .field("specifiers", [or( 240 | def("ImportSpecifier"), 241 | def("ImportNamespaceSpecifier"), 242 | def("ImportDefaultSpecifier") 243 | )], defaults.emptyArray) 244 | .field("source", def("Literal")) 245 | .field("importKind", or( 246 | "value", 247 | "type" 248 | ), function() { 249 | return "value"; 250 | }); 251 | 252 | def("ExportNamedDeclaration") 253 | .bases("Declaration") 254 | .build("declaration", "specifiers", "source") 255 | .field("declaration", or(def("Declaration"), null)) 256 | .field("specifiers", [def("ExportSpecifier")], defaults.emptyArray) 257 | .field("source", or(def("Literal"), null), defaults["null"]); 258 | 259 | def("ExportSpecifier") 260 | .bases("ModuleSpecifier") 261 | .build("local", "exported") 262 | .field("exported", def("Identifier")); 263 | 264 | def("ExportDefaultDeclaration") 265 | .bases("Declaration") 266 | .build("declaration") 267 | .field("declaration", or(def("Declaration"), def("Expression"))); 268 | 269 | def("ExportAllDeclaration") 270 | .bases("Declaration") 271 | .build("source") 272 | .field("source", def("Literal")); 273 | 274 | def("TaggedTemplateExpression") 275 | .bases("Expression") 276 | .build("tag", "quasi") 277 | .field("tag", def("Expression")) 278 | .field("quasi", def("TemplateLiteral")); 279 | 280 | def("TemplateLiteral") 281 | .bases("Expression") 282 | .build("quasis", "expressions") 283 | .field("quasis", [def("TemplateElement")]) 284 | .field("expressions", [def("Expression")]); 285 | 286 | def("TemplateElement") 287 | .bases("Node") 288 | .build("value", "tail") 289 | .field("value", {"cooked": String, "raw": String}) 290 | .field("tail", Boolean); 291 | 292 | def("MetaProperty") 293 | .bases("Expression") 294 | .build("meta", "property") 295 | .field("meta", def("Identifier")) 296 | .field("property", def("Identifier")); 297 | }; 298 | 299 | maybeSetModuleExports(() => module); 300 | -------------------------------------------------------------------------------- /src/def/esprima.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import esProposalsDef from "./es-proposals"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | 6 | export default function (fork: Fork) { 7 | fork.use(esProposalsDef); 8 | 9 | var types = fork.use(typesPlugin); 10 | var defaults = fork.use(sharedPlugin).defaults; 11 | var def = types.Type.def; 12 | var or = types.Type.or; 13 | 14 | def("VariableDeclaration") 15 | .field("declarations", [or( 16 | def("VariableDeclarator"), 17 | def("Identifier") // Esprima deviation. 18 | )]); 19 | 20 | def("Property") 21 | .field("value", or( 22 | def("Expression"), 23 | def("Pattern") // Esprima deviation. 24 | )); 25 | 26 | def("ArrayPattern") 27 | .field("elements", [or( 28 | def("Pattern"), 29 | def("SpreadElement"), 30 | null 31 | )]); 32 | 33 | def("ObjectPattern") 34 | .field("properties", [or( 35 | def("Property"), 36 | def("PropertyPattern"), 37 | def("SpreadPropertyPattern"), 38 | def("SpreadProperty") // Used by Esprima. 39 | )]); 40 | 41 | // Like ModuleSpecifier, except type:"ExportSpecifier" and buildable. 42 | // export {} [from ...]; 43 | def("ExportSpecifier") 44 | .bases("ModuleSpecifier") 45 | .build("id", "name"); 46 | 47 | // export <*> from ...; 48 | def("ExportBatchSpecifier") 49 | .bases("Specifier") 50 | .build(); 51 | 52 | def("ExportDeclaration") 53 | .bases("Declaration") 54 | .build("default", "declaration", "specifiers", "source") 55 | .field("default", Boolean) 56 | .field("declaration", or( 57 | def("Declaration"), 58 | def("Expression"), // Implies default. 59 | null 60 | )) 61 | .field("specifiers", [or( 62 | def("ExportSpecifier"), 63 | def("ExportBatchSpecifier") 64 | )], defaults.emptyArray) 65 | .field("source", or( 66 | def("Literal"), 67 | null 68 | ), defaults["null"]); 69 | 70 | def("Block") 71 | .bases("Comment") 72 | .build("value", /*optional:*/ "leading", "trailing"); 73 | 74 | def("Line") 75 | .bases("Comment") 76 | .build("value", /*optional:*/ "leading", "trailing"); 77 | }; 78 | 79 | maybeSetModuleExports(() => module); 80 | -------------------------------------------------------------------------------- /src/def/flow.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import esProposalsDef from "./es-proposals"; 3 | import typeAnnotationsDef from "./type-annotations"; 4 | import typesPlugin from "../types"; 5 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 6 | 7 | export default function (fork: Fork) { 8 | fork.use(esProposalsDef); 9 | fork.use(typeAnnotationsDef); 10 | 11 | const types = fork.use(typesPlugin); 12 | const def = types.Type.def; 13 | const or = types.Type.or; 14 | const defaults = fork.use(sharedPlugin).defaults; 15 | 16 | // Base types 17 | 18 | def("Flow").bases("Node"); 19 | def("FlowType").bases("Flow"); 20 | 21 | // Type annotations 22 | 23 | def("AnyTypeAnnotation") 24 | .bases("FlowType") 25 | .build(); 26 | 27 | def("EmptyTypeAnnotation") 28 | .bases("FlowType") 29 | .build(); 30 | 31 | def("MixedTypeAnnotation") 32 | .bases("FlowType") 33 | .build(); 34 | 35 | def("VoidTypeAnnotation") 36 | .bases("FlowType") 37 | .build(); 38 | 39 | def("SymbolTypeAnnotation") 40 | .bases("FlowType") 41 | .build(); 42 | 43 | def("NumberTypeAnnotation") 44 | .bases("FlowType") 45 | .build(); 46 | 47 | def("BigIntTypeAnnotation") 48 | .bases("FlowType") 49 | .build(); 50 | 51 | def("NumberLiteralTypeAnnotation") 52 | .bases("FlowType") 53 | .build("value", "raw") 54 | .field("value", Number) 55 | .field("raw", String); 56 | 57 | // Babylon 6 differs in AST from Flow 58 | // same as NumberLiteralTypeAnnotation 59 | def("NumericLiteralTypeAnnotation") 60 | .bases("FlowType") 61 | .build("value", "raw") 62 | .field("value", Number) 63 | .field("raw", String); 64 | 65 | def("BigIntLiteralTypeAnnotation") 66 | .bases("FlowType") 67 | .build("value", "raw") 68 | .field("value", null) 69 | .field("raw", String); 70 | 71 | def("StringTypeAnnotation") 72 | .bases("FlowType") 73 | .build(); 74 | 75 | def("StringLiteralTypeAnnotation") 76 | .bases("FlowType") 77 | .build("value", "raw") 78 | .field("value", String) 79 | .field("raw", String); 80 | 81 | def("BooleanTypeAnnotation") 82 | .bases("FlowType") 83 | .build(); 84 | 85 | def("BooleanLiteralTypeAnnotation") 86 | .bases("FlowType") 87 | .build("value", "raw") 88 | .field("value", Boolean) 89 | .field("raw", String); 90 | 91 | def("TypeAnnotation") 92 | .bases("Node") 93 | .build("typeAnnotation") 94 | .field("typeAnnotation", def("FlowType")); 95 | 96 | def("NullableTypeAnnotation") 97 | .bases("FlowType") 98 | .build("typeAnnotation") 99 | .field("typeAnnotation", def("FlowType")); 100 | 101 | def("NullLiteralTypeAnnotation") 102 | .bases("FlowType") 103 | .build(); 104 | 105 | def("NullTypeAnnotation") 106 | .bases("FlowType") 107 | .build(); 108 | 109 | def("ThisTypeAnnotation") 110 | .bases("FlowType") 111 | .build(); 112 | 113 | def("ExistsTypeAnnotation") 114 | .bases("FlowType") 115 | .build(); 116 | 117 | def("ExistentialTypeParam") 118 | .bases("FlowType") 119 | .build(); 120 | 121 | def("FunctionTypeAnnotation") 122 | .bases("FlowType") 123 | .build("params", "returnType", "rest", "typeParameters") 124 | .field("params", [def("FunctionTypeParam")]) 125 | .field("returnType", def("FlowType")) 126 | .field("rest", or(def("FunctionTypeParam"), null)) 127 | .field("typeParameters", or(def("TypeParameterDeclaration"), null)); 128 | 129 | def("FunctionTypeParam") 130 | .bases("Node") 131 | .build("name", "typeAnnotation", "optional") 132 | .field("name", or(def("Identifier"), null)) 133 | .field("typeAnnotation", def("FlowType")) 134 | .field("optional", Boolean); 135 | 136 | def("ArrayTypeAnnotation") 137 | .bases("FlowType") 138 | .build("elementType") 139 | .field("elementType", def("FlowType")); 140 | 141 | def("ObjectTypeAnnotation") 142 | .bases("FlowType") 143 | .build("properties", "indexers", "callProperties") 144 | .field("properties", [ 145 | or(def("ObjectTypeProperty"), 146 | def("ObjectTypeSpreadProperty")) 147 | ]) 148 | .field("indexers", [def("ObjectTypeIndexer")], defaults.emptyArray) 149 | .field("callProperties", 150 | [def("ObjectTypeCallProperty")], 151 | defaults.emptyArray) 152 | .field("inexact", or(Boolean, void 0), defaults["undefined"]) 153 | .field("exact", Boolean, defaults["false"]) 154 | .field("internalSlots", [def("ObjectTypeInternalSlot")], defaults.emptyArray); 155 | 156 | def("Variance") 157 | .bases("Node") 158 | .build("kind") 159 | .field("kind", or("plus", "minus")); 160 | 161 | const LegacyVariance = or( 162 | def("Variance"), 163 | "plus", 164 | "minus", 165 | null 166 | ); 167 | 168 | def("ObjectTypeProperty") 169 | .bases("Node") 170 | .build("key", "value", "optional") 171 | .field("key", or(def("Literal"), def("Identifier"))) 172 | .field("value", def("FlowType")) 173 | .field("optional", Boolean) 174 | .field("variance", LegacyVariance, defaults["null"]); 175 | 176 | def("ObjectTypeIndexer") 177 | .bases("Node") 178 | .build("id", "key", "value") 179 | .field("id", def("Identifier")) 180 | .field("key", def("FlowType")) 181 | .field("value", def("FlowType")) 182 | .field("variance", LegacyVariance, defaults["null"]) 183 | .field("static", Boolean, defaults["false"]); 184 | 185 | def("ObjectTypeCallProperty") 186 | .bases("Node") 187 | .build("value") 188 | .field("value", def("FunctionTypeAnnotation")) 189 | .field("static", Boolean, defaults["false"]); 190 | 191 | def("QualifiedTypeIdentifier") 192 | .bases("Node") 193 | .build("qualification", "id") 194 | .field("qualification", 195 | or(def("Identifier"), 196 | def("QualifiedTypeIdentifier"))) 197 | .field("id", def("Identifier")); 198 | 199 | def("GenericTypeAnnotation") 200 | .bases("FlowType") 201 | .build("id", "typeParameters") 202 | .field("id", or(def("Identifier"), def("QualifiedTypeIdentifier"))) 203 | .field("typeParameters", or(def("TypeParameterInstantiation"), null)); 204 | 205 | def("MemberTypeAnnotation") 206 | .bases("FlowType") 207 | .build("object", "property") 208 | .field("object", def("Identifier")) 209 | .field("property", 210 | or(def("MemberTypeAnnotation"), 211 | def("GenericTypeAnnotation"))); 212 | 213 | def("IndexedAccessType") 214 | .bases("FlowType") 215 | .build("objectType", "indexType") 216 | .field("objectType", def("FlowType")) 217 | .field("indexType", def("FlowType")); 218 | 219 | def("OptionalIndexedAccessType") 220 | .bases("FlowType") 221 | .build("objectType", "indexType", "optional") 222 | .field("objectType", def("FlowType")) 223 | .field("indexType", def("FlowType")) 224 | .field('optional', Boolean); 225 | 226 | def("UnionTypeAnnotation") 227 | .bases("FlowType") 228 | .build("types") 229 | .field("types", [def("FlowType")]); 230 | 231 | def("IntersectionTypeAnnotation") 232 | .bases("FlowType") 233 | .build("types") 234 | .field("types", [def("FlowType")]); 235 | 236 | def("TypeofTypeAnnotation") 237 | .bases("FlowType") 238 | .build("argument") 239 | .field("argument", def("FlowType")); 240 | 241 | def("ObjectTypeSpreadProperty") 242 | .bases("Node") 243 | .build("argument") 244 | .field("argument", def("FlowType")); 245 | 246 | def("ObjectTypeInternalSlot") 247 | .bases("Node") 248 | .build("id", "value", "optional", "static", "method") 249 | .field("id", def("Identifier")) 250 | .field("value", def("FlowType")) 251 | .field("optional", Boolean) 252 | .field("static", Boolean) 253 | .field("method", Boolean); 254 | 255 | def("TypeParameterDeclaration") 256 | .bases("Node") 257 | .build("params") 258 | .field("params", [def("TypeParameter")]); 259 | 260 | def("TypeParameterInstantiation") 261 | .bases("Node") 262 | .build("params") 263 | .field("params", [def("FlowType")]); 264 | 265 | def("TypeParameter") 266 | .bases("FlowType") 267 | .build("name", "variance", "bound", "default") 268 | .field("name", String) 269 | .field("variance", LegacyVariance, defaults["null"]) 270 | .field("bound", or(def("TypeAnnotation"), null), defaults["null"]) 271 | .field("default", or(def("FlowType"), null), defaults["null"]); 272 | 273 | def("ClassProperty") 274 | .field("variance", LegacyVariance, defaults["null"]); 275 | 276 | def("ClassImplements") 277 | .bases("Node") 278 | .build("id") 279 | .field("id", def("Identifier")) 280 | .field("superClass", or(def("Expression"), null), defaults["null"]) 281 | .field("typeParameters", 282 | or(def("TypeParameterInstantiation"), null), 283 | defaults["null"]); 284 | 285 | def("InterfaceTypeAnnotation") 286 | .bases("FlowType") 287 | .build("body", "extends") 288 | .field("body", def("ObjectTypeAnnotation")) 289 | .field("extends", or([def("InterfaceExtends")], null), defaults["null"]); 290 | 291 | def("InterfaceDeclaration") 292 | .bases("Declaration") 293 | .build("id", "body", "extends") 294 | .field("id", def("Identifier")) 295 | .field("typeParameters", 296 | or(def("TypeParameterDeclaration"), null), 297 | defaults["null"]) 298 | .field("body", def("ObjectTypeAnnotation")) 299 | .field("extends", [def("InterfaceExtends")]); 300 | 301 | def("DeclareInterface") 302 | .bases("InterfaceDeclaration") 303 | .build("id", "body", "extends"); 304 | 305 | def("InterfaceExtends") 306 | .bases("Node") 307 | .build("id") 308 | .field("id", def("Identifier")) 309 | .field("typeParameters", 310 | or(def("TypeParameterInstantiation"), null), 311 | defaults["null"]); 312 | 313 | def("TypeAlias") 314 | .bases("Declaration") 315 | .build("id", "typeParameters", "right") 316 | .field("id", def("Identifier")) 317 | .field("typeParameters", or(def("TypeParameterDeclaration"), null)) 318 | .field("right", def("FlowType")); 319 | 320 | def("DeclareTypeAlias") 321 | .bases("TypeAlias") 322 | .build("id", "typeParameters", "right"); 323 | 324 | def("OpaqueType") 325 | .bases("Declaration") 326 | .build("id", "typeParameters", "impltype", "supertype") 327 | .field("id", def("Identifier")) 328 | .field("typeParameters", or(def("TypeParameterDeclaration"), null)) 329 | .field("impltype", def("FlowType")) 330 | .field("supertype", or(def("FlowType"), null)); 331 | 332 | def("DeclareOpaqueType") 333 | .bases("OpaqueType") 334 | .build("id", "typeParameters", "supertype") 335 | .field("impltype", or(def("FlowType"), null)); 336 | 337 | def("TypeCastExpression") 338 | .bases("Expression") 339 | .build("expression", "typeAnnotation") 340 | .field("expression", def("Expression")) 341 | .field("typeAnnotation", def("TypeAnnotation")); 342 | 343 | def("TupleTypeAnnotation") 344 | .bases("FlowType") 345 | .build("types") 346 | .field("types", [def("FlowType")]); 347 | 348 | def("DeclareVariable") 349 | .bases("Statement") 350 | .build("id") 351 | .field("id", def("Identifier")); 352 | 353 | def("DeclareFunction") 354 | .bases("Statement") 355 | .build("id") 356 | .field("id", def("Identifier")) 357 | .field("predicate", or(def("FlowPredicate"), null), defaults["null"]); 358 | 359 | def("DeclareClass") 360 | .bases("InterfaceDeclaration") 361 | .build("id"); 362 | 363 | def("DeclareModule") 364 | .bases("Statement") 365 | .build("id", "body") 366 | .field("id", or(def("Identifier"), def("Literal"))) 367 | .field("body", def("BlockStatement")); 368 | 369 | def("DeclareModuleExports") 370 | .bases("Statement") 371 | .build("typeAnnotation") 372 | .field("typeAnnotation", def("TypeAnnotation")); 373 | 374 | def("DeclareExportDeclaration") 375 | .bases("Declaration") 376 | .build("default", "declaration", "specifiers", "source") 377 | .field("default", Boolean) 378 | .field("declaration", or( 379 | def("DeclareVariable"), 380 | def("DeclareFunction"), 381 | def("DeclareClass"), 382 | def("FlowType"), // Implies default. 383 | def("TypeAlias"), // Implies named type 384 | def("DeclareOpaqueType"), // Implies named opaque type 385 | def("InterfaceDeclaration"), 386 | null 387 | )) 388 | .field("specifiers", [or( 389 | def("ExportSpecifier"), 390 | def("ExportBatchSpecifier") 391 | )], defaults.emptyArray) 392 | .field("source", or( 393 | def("Literal"), 394 | null 395 | ), defaults["null"]); 396 | 397 | def("DeclareExportAllDeclaration") 398 | .bases("Declaration") 399 | .build("source") 400 | .field("source", or( 401 | def("Literal"), 402 | null 403 | ), defaults["null"]); 404 | 405 | def("ImportDeclaration") 406 | .field("importKind", or("value", "type", "typeof"), () => "value"); 407 | 408 | def("FlowPredicate").bases("Flow"); 409 | 410 | def("InferredPredicate") 411 | .bases("FlowPredicate") 412 | .build(); 413 | 414 | def("DeclaredPredicate") 415 | .bases("FlowPredicate") 416 | .build("value") 417 | .field("value", def("Expression")); 418 | 419 | def("Function") 420 | .field("predicate", or(def("FlowPredicate"), null), defaults["null"]); 421 | 422 | def("CallExpression") 423 | .field("typeArguments", or( 424 | null, 425 | def("TypeParameterInstantiation"), 426 | ), defaults["null"]); 427 | 428 | def("NewExpression") 429 | .field("typeArguments", or( 430 | null, 431 | def("TypeParameterInstantiation"), 432 | ), defaults["null"]); 433 | 434 | // Enums 435 | def("EnumDeclaration") 436 | .bases("Declaration") 437 | .build("id", "body") 438 | .field("id", def("Identifier")) 439 | .field("body", or( 440 | def("EnumBooleanBody"), 441 | def("EnumNumberBody"), 442 | def("EnumStringBody"), 443 | def("EnumSymbolBody"))); 444 | 445 | def("EnumBooleanBody") 446 | .build("members", "explicitType") 447 | .field("members", [def("EnumBooleanMember")]) 448 | .field("explicitType", Boolean); 449 | 450 | def("EnumNumberBody") 451 | .build("members", "explicitType") 452 | .field("members", [def("EnumNumberMember")]) 453 | .field("explicitType", Boolean); 454 | 455 | def("EnumStringBody") 456 | .build("members", "explicitType") 457 | .field("members", or([def("EnumStringMember")], [def("EnumDefaultedMember")])) 458 | .field("explicitType", Boolean); 459 | 460 | def("EnumSymbolBody") 461 | .build("members") 462 | .field("members", [def("EnumDefaultedMember")]); 463 | 464 | def("EnumBooleanMember") 465 | .build("id", "init") 466 | .field("id", def("Identifier")) 467 | .field("init", or(def("Literal"), Boolean)); 468 | 469 | def("EnumNumberMember") 470 | .build("id", "init") 471 | .field("id", def("Identifier")) 472 | .field("init", def("Literal")); 473 | 474 | def("EnumStringMember") 475 | .build("id", "init") 476 | .field("id", def("Identifier")) 477 | .field("init", def("Literal")); 478 | 479 | def("EnumDefaultedMember") 480 | .build("id") 481 | .field("id", def("Identifier")); 482 | }; 483 | 484 | maybeSetModuleExports(() => module); 485 | -------------------------------------------------------------------------------- /src/def/jsx.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import esProposalsDef from "./es-proposals"; 3 | import typesPlugin from "../types"; 4 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 5 | import { namedTypes as N } from "../gen/namedTypes"; 6 | 7 | export default function (fork: Fork) { 8 | fork.use(esProposalsDef); 9 | 10 | const types = fork.use(typesPlugin); 11 | const def = types.Type.def; 12 | const or = types.Type.or; 13 | const defaults = fork.use(sharedPlugin).defaults; 14 | 15 | def("JSXAttribute") 16 | .bases("Node") 17 | .build("name", "value") 18 | .field("name", or(def("JSXIdentifier"), def("JSXNamespacedName"))) 19 | .field("value", or( 20 | def("Literal"), // attr="value" 21 | def("JSXExpressionContainer"), // attr={value} 22 | def("JSXElement"), // attr=
23 | def("JSXFragment"), // attr=<> 24 | null // attr= or just attr 25 | ), defaults["null"]); 26 | 27 | def("JSXIdentifier") 28 | .bases("Identifier") 29 | .build("name") 30 | .field("name", String); 31 | 32 | def("JSXNamespacedName") 33 | .bases("Node") 34 | .build("namespace", "name") 35 | .field("namespace", def("JSXIdentifier")) 36 | .field("name", def("JSXIdentifier")); 37 | 38 | def("JSXMemberExpression") 39 | .bases("MemberExpression") 40 | .build("object", "property") 41 | .field("object", or(def("JSXIdentifier"), def("JSXMemberExpression"))) 42 | .field("property", def("JSXIdentifier")) 43 | .field("computed", Boolean, defaults.false); 44 | 45 | const JSXElementName = or( 46 | def("JSXIdentifier"), 47 | def("JSXNamespacedName"), 48 | def("JSXMemberExpression") 49 | ); 50 | 51 | def("JSXSpreadAttribute") 52 | .bases("Node") 53 | .build("argument") 54 | .field("argument", def("Expression")); 55 | 56 | const JSXAttributes = [or( 57 | def("JSXAttribute"), 58 | def("JSXSpreadAttribute") 59 | )]; 60 | 61 | def("JSXExpressionContainer") 62 | .bases("Expression") 63 | .build("expression") 64 | .field("expression", or(def("Expression"), def("JSXEmptyExpression"))); 65 | 66 | const JSXChildren = [or( 67 | def("JSXText"), 68 | def("JSXExpressionContainer"), 69 | def("JSXSpreadChild"), 70 | def("JSXElement"), 71 | def("JSXFragment"), 72 | def("Literal") // Legacy: Esprima should return JSXText instead. 73 | )]; 74 | 75 | def("JSXElement") 76 | .bases("Expression") 77 | .build("openingElement", "closingElement", "children") 78 | .field("openingElement", def("JSXOpeningElement")) 79 | .field("closingElement", or(def("JSXClosingElement"), null), defaults["null"]) 80 | .field("children", JSXChildren, defaults.emptyArray) 81 | .field("name", JSXElementName, function (this: N.JSXElement) { 82 | // Little-known fact: the `this` object inside a default function 83 | // is none other than the partially-built object itself, and any 84 | // fields initialized directly from builder function arguments 85 | // (like openingElement, closingElement, and children) are 86 | // guaranteed to be available. 87 | return this.openingElement.name; 88 | }, true) // hidden from traversal 89 | .field("selfClosing", Boolean, function (this: N.JSXElement) { 90 | return this.openingElement.selfClosing; 91 | }, true) // hidden from traversal 92 | .field("attributes", JSXAttributes, function (this: N.JSXElement) { 93 | return this.openingElement.attributes; 94 | }, true); // hidden from traversal 95 | 96 | def("JSXOpeningElement") 97 | .bases("Node") 98 | .build("name", "attributes", "selfClosing") 99 | .field("name", JSXElementName) 100 | .field("attributes", JSXAttributes, defaults.emptyArray) 101 | .field("selfClosing", Boolean, defaults["false"]); 102 | 103 | def("JSXClosingElement") 104 | .bases("Node") 105 | .build("name") 106 | .field("name", JSXElementName); 107 | 108 | def("JSXFragment") 109 | .bases("Expression") 110 | .build("openingFragment", "closingFragment", "children") 111 | .field("openingFragment", def("JSXOpeningFragment")) 112 | .field("closingFragment", def("JSXClosingFragment")) 113 | .field("children", JSXChildren, defaults.emptyArray); 114 | 115 | def("JSXOpeningFragment") 116 | .bases("Node") 117 | .build(); 118 | 119 | def("JSXClosingFragment") 120 | .bases("Node") 121 | .build(); 122 | 123 | def("JSXText") 124 | .bases("Literal") 125 | .build("value", "raw") 126 | .field("value", String) 127 | .field("raw", String, function (this: N.JSXText) { 128 | return this.value; 129 | }); 130 | 131 | def("JSXEmptyExpression") 132 | .bases("Node") 133 | .build(); 134 | 135 | def("JSXSpreadChild") 136 | .bases("Node") 137 | .build("expression") 138 | .field("expression", def("Expression")); 139 | }; 140 | 141 | maybeSetModuleExports(() => module); 142 | -------------------------------------------------------------------------------- /src/def/operators/core.ts: -------------------------------------------------------------------------------- 1 | import { maybeSetModuleExports } from "../../shared"; 2 | 3 | export default function () { 4 | return { 5 | BinaryOperators: [ 6 | "==", "!=", "===", "!==", 7 | "<", "<=", ">", ">=", 8 | "<<", ">>", ">>>", 9 | "+", "-", "*", "/", "%", 10 | "&", 11 | "|", "^", "in", 12 | "instanceof", 13 | ], 14 | 15 | AssignmentOperators: [ 16 | "=", "+=", "-=", "*=", "/=", "%=", 17 | "<<=", ">>=", ">>>=", 18 | "|=", "^=", "&=", 19 | ], 20 | 21 | LogicalOperators: [ 22 | "||", "&&", 23 | ], 24 | }; 25 | } 26 | 27 | maybeSetModuleExports(() => module); 28 | -------------------------------------------------------------------------------- /src/def/operators/es2016.ts: -------------------------------------------------------------------------------- 1 | import { maybeSetModuleExports } from "../../shared"; 2 | import coreOpsDef from "./core"; 3 | 4 | export default function (fork: import("../../types").Fork) { 5 | const result = fork.use(coreOpsDef); 6 | 7 | // Exponentiation operators. Must run before BinaryOperators or 8 | // AssignmentOperators are used (hence before fork.use(es6Def)). 9 | // https://github.com/tc39/proposal-exponentiation-operator 10 | if (result.BinaryOperators.indexOf("**") < 0) { 11 | result.BinaryOperators.push("**"); 12 | } 13 | if (result.AssignmentOperators.indexOf("**=") < 0) { 14 | result.AssignmentOperators.push("**="); 15 | } 16 | 17 | return result; 18 | } 19 | 20 | maybeSetModuleExports(() => module); 21 | -------------------------------------------------------------------------------- /src/def/operators/es2020.ts: -------------------------------------------------------------------------------- 1 | import { maybeSetModuleExports } from "../../shared"; 2 | import es2016OpsDef from "./es2016"; 3 | 4 | export default function (fork: import("../../types").Fork) { 5 | const result = fork.use(es2016OpsDef); 6 | 7 | // Nullish coalescing. Must run before LogicalOperators is used. 8 | // https://github.com/tc39/proposal-nullish-coalescing 9 | if (result.LogicalOperators.indexOf("??") < 0) { 10 | result.LogicalOperators.push("??"); 11 | } 12 | 13 | return result; 14 | } 15 | 16 | maybeSetModuleExports(() => module); 17 | -------------------------------------------------------------------------------- /src/def/operators/es2021.ts: -------------------------------------------------------------------------------- 1 | import { maybeSetModuleExports } from "../../shared"; 2 | import es2020OpsDef from "./es2020"; 3 | 4 | export default function (fork: import("../../types").Fork) { 5 | const result = fork.use(es2020OpsDef); 6 | 7 | // Logical assignment operators. Must run before AssignmentOperators is used. 8 | // https://github.com/tc39/proposal-logical-assignment 9 | result.LogicalOperators.forEach(op => { 10 | const assignOp = op + "="; 11 | if (result.AssignmentOperators.indexOf(assignOp) < 0) { 12 | result.AssignmentOperators.push(assignOp); 13 | } 14 | }); 15 | 16 | return result; 17 | } 18 | 19 | maybeSetModuleExports(() => module); 20 | -------------------------------------------------------------------------------- /src/def/type-annotations.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type annotation defs shared between Flow and TypeScript. 3 | * These defs could not be defined in ./flow.ts or ./typescript.ts directly 4 | * because they use the same name. 5 | */ 6 | 7 | import { Fork } from "../types"; 8 | import typesPlugin from "../types"; 9 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 10 | 11 | export default function (fork: Fork) { 12 | var types = fork.use(typesPlugin); 13 | var def = types.Type.def; 14 | var or = types.Type.or; 15 | var defaults = fork.use(sharedPlugin).defaults; 16 | 17 | var TypeAnnotation = or( 18 | def("TypeAnnotation"), 19 | def("TSTypeAnnotation"), 20 | null 21 | ); 22 | 23 | var TypeParamDecl = or( 24 | def("TypeParameterDeclaration"), 25 | def("TSTypeParameterDeclaration"), 26 | null 27 | ); 28 | 29 | def("Identifier") 30 | .field("typeAnnotation", TypeAnnotation, defaults["null"]); 31 | 32 | def("ObjectPattern") 33 | .field("typeAnnotation", TypeAnnotation, defaults["null"]); 34 | 35 | def("Function") 36 | .field("returnType", TypeAnnotation, defaults["null"]) 37 | .field("typeParameters", TypeParamDecl, defaults["null"]); 38 | 39 | def("ClassProperty") 40 | .build("key", "value", "typeAnnotation", "static") 41 | .field("value", or(def("Expression"), null)) 42 | .field("static", Boolean, defaults["false"]) 43 | .field("typeAnnotation", TypeAnnotation, defaults["null"]); 44 | 45 | ["ClassDeclaration", 46 | "ClassExpression", 47 | ].forEach(typeName => { 48 | def(typeName) 49 | .field("typeParameters", TypeParamDecl, defaults["null"]) 50 | .field("superTypeParameters", 51 | or(def("TypeParameterInstantiation"), 52 | def("TSTypeParameterInstantiation"), 53 | null), 54 | defaults["null"]) 55 | .field("implements", 56 | or([def("ClassImplements")], 57 | [def("TSExpressionWithTypeArguments")]), 58 | defaults.emptyArray); 59 | }); 60 | }; 61 | 62 | maybeSetModuleExports(() => module); 63 | -------------------------------------------------------------------------------- /src/def/typescript.ts: -------------------------------------------------------------------------------- 1 | import { Fork } from "../types"; 2 | import babelCoreDef from "./babel-core"; 3 | import typeAnnotationsDef from "./type-annotations"; 4 | import typesPlugin from "../types"; 5 | import sharedPlugin, { maybeSetModuleExports } from "../shared"; 6 | 7 | export default function (fork: Fork) { 8 | // Since TypeScript is parsed by Babylon, include the core Babylon types 9 | // but omit the Flow-related types. 10 | fork.use(babelCoreDef); 11 | fork.use(typeAnnotationsDef); 12 | 13 | var types = fork.use(typesPlugin); 14 | var n = types.namedTypes; 15 | var def = types.Type.def; 16 | var or = types.Type.or; 17 | var defaults = fork.use(sharedPlugin).defaults; 18 | var StringLiteral = types.Type.from(function (value: any, deep?: any) { 19 | if (n.StringLiteral && 20 | n.StringLiteral.check(value, deep)) { 21 | return true 22 | } 23 | if (n.Literal && 24 | n.Literal.check(value, deep) && 25 | typeof value.value === "string") { 26 | return true; 27 | } 28 | return false; 29 | }, "StringLiteral"); 30 | 31 | def("TSType") 32 | .bases("Node"); 33 | 34 | var TSEntityName = or( 35 | def("Identifier"), 36 | def("TSQualifiedName") 37 | ); 38 | 39 | def("TSTypeReference") 40 | .bases("TSType", "TSHasOptionalTypeParameterInstantiation") 41 | .build("typeName", "typeParameters") 42 | .field("typeName", TSEntityName); 43 | 44 | // An abstract (non-buildable) base type that provide a commonly-needed 45 | // optional .typeParameters field. 46 | def("TSHasOptionalTypeParameterInstantiation") 47 | .field("typeParameters", 48 | or(def("TSTypeParameterInstantiation"), null), 49 | defaults["null"]); 50 | 51 | // An abstract (non-buildable) base type that provide a commonly-needed 52 | // optional .typeParameters field. 53 | def("TSHasOptionalTypeParameters") 54 | .field("typeParameters", 55 | or(def("TSTypeParameterDeclaration"), null, void 0), 56 | defaults["null"]); 57 | 58 | // An abstract (non-buildable) base type that provide a commonly-needed 59 | // optional .typeAnnotation field. 60 | def("TSHasOptionalTypeAnnotation") 61 | .field("typeAnnotation", 62 | or(def("TSTypeAnnotation"), null), 63 | defaults["null"]); 64 | 65 | def("TSQualifiedName") 66 | .bases("Node") 67 | .build("left", "right") 68 | .field("left", TSEntityName) 69 | .field("right", TSEntityName); 70 | 71 | def("TSAsExpression") 72 | .bases("Expression", "Pattern") 73 | .build("expression", "typeAnnotation") 74 | .field("expression", def("Expression")) 75 | .field("typeAnnotation", def("TSType")) 76 | .field("extra", 77 | or({ parenthesized: Boolean }, null), 78 | defaults["null"]); 79 | 80 | def("TSTypeCastExpression") 81 | .bases("Expression") 82 | .build("expression", "typeAnnotation") 83 | .field("expression", def("Expression")) 84 | .field("typeAnnotation", def("TSType")); 85 | 86 | def("TSSatisfiesExpression") 87 | .bases("Expression", "Pattern") 88 | .build("expression", "typeAnnotation") 89 | .field("expression", def("Expression")) 90 | .field("typeAnnotation", def("TSType")); 91 | 92 | def("TSNonNullExpression") 93 | .bases("Expression", "Pattern") 94 | .build("expression") 95 | .field("expression", def("Expression")); 96 | 97 | [ // Define all the simple keyword types. 98 | "TSAnyKeyword", 99 | "TSBigIntKeyword", 100 | "TSBooleanKeyword", 101 | "TSNeverKeyword", 102 | "TSNullKeyword", 103 | "TSNumberKeyword", 104 | "TSObjectKeyword", 105 | "TSStringKeyword", 106 | "TSSymbolKeyword", 107 | "TSUndefinedKeyword", 108 | "TSUnknownKeyword", 109 | "TSVoidKeyword", 110 | "TSIntrinsicKeyword", 111 | "TSThisType", 112 | ].forEach(keywordType => { 113 | def(keywordType) 114 | .bases("TSType") 115 | .build(); 116 | }); 117 | 118 | def("TSArrayType") 119 | .bases("TSType") 120 | .build("elementType") 121 | .field("elementType", def("TSType")) 122 | 123 | def("TSLiteralType") 124 | .bases("TSType") 125 | .build("literal") 126 | .field("literal", or( 127 | def("NumericLiteral"), 128 | def("StringLiteral"), 129 | def("BooleanLiteral"), 130 | def("TemplateLiteral"), 131 | def("UnaryExpression"), 132 | def("BigIntLiteral"), 133 | )); 134 | 135 | def("TemplateLiteral") 136 | // The TemplateLiteral type appears to be reused for TypeScript template 137 | // literal types (instead of introducing a new TSTemplateLiteralType type), 138 | // so we allow the templateLiteral.expressions array to be either all 139 | // expressions or all TypeScript types. 140 | .field("expressions", or( 141 | [def("Expression")], 142 | [def("TSType")], 143 | )); 144 | 145 | ["TSUnionType", 146 | "TSIntersectionType", 147 | ].forEach(typeName => { 148 | def(typeName) 149 | .bases("TSType") 150 | .build("types") 151 | .field("types", [def("TSType")]); 152 | }); 153 | 154 | def("TSConditionalType") 155 | .bases("TSType") 156 | .build("checkType", "extendsType", "trueType", "falseType") 157 | .field("checkType", def("TSType")) 158 | .field("extendsType", def("TSType")) 159 | .field("trueType", def("TSType")) 160 | .field("falseType", def("TSType")); 161 | 162 | def("TSInferType") 163 | .bases("TSType") 164 | .build("typeParameter") 165 | .field("typeParameter", def("TSTypeParameter")); 166 | 167 | def("TSParenthesizedType") 168 | .bases("TSType") 169 | .build("typeAnnotation") 170 | .field("typeAnnotation", def("TSType")); 171 | 172 | var ParametersType = [or( 173 | def("Identifier"), 174 | def("RestElement"), 175 | def("ArrayPattern"), 176 | def("ObjectPattern") 177 | )]; 178 | 179 | ["TSFunctionType", 180 | "TSConstructorType", 181 | ].forEach(typeName => { 182 | def(typeName) 183 | .bases("TSType", 184 | "TSHasOptionalTypeParameters", 185 | "TSHasOptionalTypeAnnotation") 186 | .build("parameters") 187 | .field("parameters", ParametersType); 188 | }); 189 | 190 | def("TSDeclareFunction") 191 | .bases("Declaration", "TSHasOptionalTypeParameters") 192 | .build("id", "params", "returnType") 193 | .field("declare", Boolean, defaults["false"]) 194 | .field("async", Boolean, defaults["false"]) 195 | .field("generator", Boolean, defaults["false"]) 196 | .field("id", or(def("Identifier"), null), defaults["null"]) 197 | .field("params", [def("Pattern")]) 198 | // tSFunctionTypeAnnotationCommon 199 | .field("returnType", 200 | or(def("TSTypeAnnotation"), 201 | def("Noop"), // Still used? 202 | null), 203 | defaults["null"]); 204 | 205 | def("TSDeclareMethod") 206 | .bases("Declaration", "TSHasOptionalTypeParameters") 207 | .build("key", "params", "returnType") 208 | .field("async", Boolean, defaults["false"]) 209 | .field("generator", Boolean, defaults["false"]) 210 | .field("params", [def("Pattern")]) 211 | // classMethodOrPropertyCommon 212 | .field("abstract", Boolean, defaults["false"]) 213 | .field("accessibility", 214 | or("public", "private", "protected", void 0), 215 | defaults["undefined"]) 216 | .field("static", Boolean, defaults["false"]) 217 | .field("computed", Boolean, defaults["false"]) 218 | .field("optional", Boolean, defaults["false"]) 219 | .field("key", or( 220 | def("Identifier"), 221 | def("StringLiteral"), 222 | def("NumericLiteral"), 223 | // Only allowed if .computed is true. 224 | def("Expression") 225 | )) 226 | // classMethodOrDeclareMethodCommon 227 | .field("kind", 228 | or("get", "set", "method", "constructor"), 229 | function getDefault() { return "method"; }) 230 | .field("access", // Not "accessibility"? 231 | or("public", "private", "protected", void 0), 232 | defaults["undefined"]) 233 | .field("decorators", 234 | or([def("Decorator")], null), 235 | defaults["null"]) 236 | // tSFunctionTypeAnnotationCommon 237 | .field("returnType", 238 | or(def("TSTypeAnnotation"), 239 | def("Noop"), // Still used? 240 | null), 241 | defaults["null"]); 242 | 243 | def("TSMappedType") 244 | .bases("TSType") 245 | .build("typeParameter", "typeAnnotation") 246 | .field("readonly", or(Boolean, "+", "-"), defaults["false"]) 247 | .field("typeParameter", def("TSTypeParameter")) 248 | .field("optional", or(Boolean, "+", "-"), defaults["false"]) 249 | .field("typeAnnotation", 250 | or(def("TSType"), null), 251 | defaults["null"]); 252 | 253 | def("TSTupleType") 254 | .bases("TSType") 255 | .build("elementTypes") 256 | .field("elementTypes", [or( 257 | def("TSType"), 258 | def("TSNamedTupleMember") 259 | )]); 260 | 261 | def("TSNamedTupleMember") 262 | .bases("TSType") 263 | .build("label", "elementType", "optional") 264 | .field("label", def("Identifier")) 265 | .field("optional", Boolean, defaults["false"]) 266 | .field("elementType", def("TSType")); 267 | 268 | def("TSRestType") 269 | .bases("TSType") 270 | .build("typeAnnotation") 271 | .field("typeAnnotation", def("TSType")); 272 | 273 | def("TSOptionalType") 274 | .bases("TSType") 275 | .build("typeAnnotation") 276 | .field("typeAnnotation", def("TSType")); 277 | 278 | def("TSIndexedAccessType") 279 | .bases("TSType") 280 | .build("objectType", "indexType") 281 | .field("objectType", def("TSType")) 282 | .field("indexType", def("TSType")) 283 | 284 | def("TSTypeOperator") 285 | .bases("TSType") 286 | .build("operator") 287 | .field("operator", String) 288 | .field("typeAnnotation", def("TSType")); 289 | 290 | def("TSTypeAnnotation") 291 | .bases("Node") 292 | .build("typeAnnotation") 293 | .field("typeAnnotation", 294 | or(def("TSType"), 295 | def("TSTypeAnnotation"))); 296 | 297 | def("TSIndexSignature") 298 | .bases("Declaration", "TSHasOptionalTypeAnnotation") 299 | .build("parameters", "typeAnnotation") 300 | .field("parameters", [def("Identifier")]) // Length === 1 301 | .field("readonly", Boolean, defaults["false"]); 302 | 303 | def("TSPropertySignature") 304 | .bases("Declaration", "TSHasOptionalTypeAnnotation") 305 | .build("key", "typeAnnotation", "optional") 306 | .field("key", def("Expression")) 307 | .field("computed", Boolean, defaults["false"]) 308 | .field("readonly", Boolean, defaults["false"]) 309 | .field("optional", Boolean, defaults["false"]) 310 | .field("initializer", 311 | or(def("Expression"), null), 312 | defaults["null"]); 313 | 314 | def("TSMethodSignature") 315 | .bases("Declaration", 316 | "TSHasOptionalTypeParameters", 317 | "TSHasOptionalTypeAnnotation") 318 | .build("key", "parameters", "typeAnnotation") 319 | .field("key", def("Expression")) 320 | .field("computed", Boolean, defaults["false"]) 321 | .field("optional", Boolean, defaults["false"]) 322 | .field("parameters", ParametersType); 323 | 324 | def("TSTypePredicate") 325 | .bases("TSTypeAnnotation", "TSType") 326 | .build("parameterName", "typeAnnotation", "asserts") 327 | .field("parameterName", 328 | or(def("Identifier"), 329 | def("TSThisType"))) 330 | .field("typeAnnotation", or(def("TSTypeAnnotation"), null), 331 | defaults["null"]) 332 | .field("asserts", Boolean, defaults["false"]); 333 | 334 | ["TSCallSignatureDeclaration", 335 | "TSConstructSignatureDeclaration", 336 | ].forEach(typeName => { 337 | def(typeName) 338 | .bases("Declaration", 339 | "TSHasOptionalTypeParameters", 340 | "TSHasOptionalTypeAnnotation") 341 | .build("parameters", "typeAnnotation") 342 | .field("parameters", ParametersType); 343 | }); 344 | 345 | def("TSEnumMember") 346 | .bases("Node") 347 | .build("id", "initializer") 348 | .field("id", or(def("Identifier"), StringLiteral)) 349 | .field("initializer", 350 | or(def("Expression"), null), 351 | defaults["null"]); 352 | 353 | def("TSTypeQuery") 354 | .bases("TSType") 355 | .build("exprName") 356 | .field("exprName", or(TSEntityName, def("TSImportType"))); 357 | 358 | // Inferred from Babylon's tsParseTypeMember method. 359 | var TSTypeMember = or( 360 | def("TSCallSignatureDeclaration"), 361 | def("TSConstructSignatureDeclaration"), 362 | def("TSIndexSignature"), 363 | def("TSMethodSignature"), 364 | def("TSPropertySignature") 365 | ); 366 | 367 | def("TSTypeLiteral") 368 | .bases("TSType") 369 | .build("members") 370 | .field("members", [TSTypeMember]); 371 | 372 | def("TSTypeParameter") 373 | .bases("Identifier") 374 | .build("name", "constraint", "default") 375 | .field("name", or(def("Identifier"), String)) 376 | .field("constraint", or(def("TSType"), void 0), defaults["undefined"]) 377 | .field("default", or(def("TSType"), void 0), defaults["undefined"]); 378 | 379 | def("TSTypeAssertion") 380 | .bases("Expression", "Pattern") 381 | .build("typeAnnotation", "expression") 382 | .field("typeAnnotation", def("TSType")) 383 | .field("expression", def("Expression")) 384 | .field("extra", 385 | or({ parenthesized: Boolean }, null), 386 | defaults["null"]); 387 | 388 | def("TSTypeParameterDeclaration") 389 | .bases("Declaration") 390 | .build("params") 391 | .field("params", [def("TSTypeParameter")]); 392 | 393 | def("TSInstantiationExpression") 394 | .bases("Expression", "TSHasOptionalTypeParameterInstantiation") 395 | .build("expression", "typeParameters") 396 | .field("expression", def("Expression")); 397 | 398 | def("TSTypeParameterInstantiation") 399 | .bases("Node") 400 | .build("params") 401 | .field("params", [def("TSType")]); 402 | 403 | def("TSEnumDeclaration") 404 | .bases("Declaration") 405 | .build("id", "members") 406 | .field("id", def("Identifier")) 407 | .field("const", Boolean, defaults["false"]) 408 | .field("declare", Boolean, defaults["false"]) 409 | .field("members", [def("TSEnumMember")]) 410 | .field("initializer", 411 | or(def("Expression"), null), 412 | defaults["null"]); 413 | 414 | def("TSTypeAliasDeclaration") 415 | .bases("Declaration", "TSHasOptionalTypeParameters") 416 | .build("id", "typeAnnotation") 417 | .field("id", def("Identifier")) 418 | .field("declare", Boolean, defaults["false"]) 419 | .field("typeAnnotation", def("TSType")); 420 | 421 | def("TSModuleBlock") 422 | .bases("Node") 423 | .build("body") 424 | .field("body", [def("Statement")]); 425 | 426 | def("TSModuleDeclaration") 427 | .bases("Declaration") 428 | .build("id", "body") 429 | .field("id", or(StringLiteral, TSEntityName)) 430 | .field("declare", Boolean, defaults["false"]) 431 | .field("global", Boolean, defaults["false"]) 432 | .field("body", 433 | or(def("TSModuleBlock"), 434 | def("TSModuleDeclaration"), 435 | null), 436 | defaults["null"]); 437 | 438 | def("TSImportType") 439 | .bases("TSType", "TSHasOptionalTypeParameterInstantiation") 440 | .build("argument", "qualifier", "typeParameters") 441 | .field("argument", StringLiteral) 442 | .field("qualifier", or(TSEntityName, void 0), defaults["undefined"]); 443 | 444 | def("TSImportEqualsDeclaration") 445 | .bases("Declaration") 446 | .build("id", "moduleReference") 447 | .field("id", def("Identifier")) 448 | .field("isExport", Boolean, defaults["false"]) 449 | .field("moduleReference", 450 | or(TSEntityName, 451 | def("TSExternalModuleReference"))); 452 | 453 | def("TSExternalModuleReference") 454 | .bases("Declaration") 455 | .build("expression") 456 | .field("expression", StringLiteral); 457 | 458 | def("TSExportAssignment") 459 | .bases("Statement") 460 | .build("expression") 461 | .field("expression", def("Expression")); 462 | 463 | def("TSNamespaceExportDeclaration") 464 | .bases("Declaration") 465 | .build("id") 466 | .field("id", def("Identifier")); 467 | 468 | def("TSInterfaceBody") 469 | .bases("Node") 470 | .build("body") 471 | .field("body", [TSTypeMember]); 472 | 473 | def("TSExpressionWithTypeArguments") 474 | .bases("TSType", "TSHasOptionalTypeParameterInstantiation") 475 | .build("expression", "typeParameters") 476 | .field("expression", TSEntityName); 477 | 478 | def("TSInterfaceDeclaration") 479 | .bases("Declaration", "TSHasOptionalTypeParameters") 480 | .build("id", "body") 481 | .field("id", TSEntityName) 482 | .field("declare", Boolean, defaults["false"]) 483 | .field("extends", 484 | or([def("TSExpressionWithTypeArguments")], null), 485 | defaults["null"]) 486 | .field("body", def("TSInterfaceBody")); 487 | 488 | def("TSParameterProperty") 489 | .bases("Pattern") 490 | .build("parameter") 491 | .field("accessibility", 492 | or("public", "private", "protected", void 0), 493 | defaults["undefined"]) 494 | .field("readonly", Boolean, defaults["false"]) 495 | .field("parameter", or(def("Identifier"), 496 | def("AssignmentPattern"))); 497 | 498 | def("ClassProperty") 499 | .field("access", // Not "accessibility"? 500 | or("public", "private", "protected", void 0), 501 | defaults["undefined"]); 502 | 503 | def("ClassAccessorProperty") 504 | .bases("Declaration", "TSHasOptionalTypeAnnotation"); 505 | 506 | // Defined already in es6 and babel-core. 507 | def("ClassBody") 508 | .field("body", [or( 509 | def("MethodDefinition"), 510 | def("VariableDeclarator"), 511 | def("ClassPropertyDefinition"), 512 | def("ClassProperty"), 513 | def("ClassPrivateProperty"), 514 | def("ClassAccessorProperty"), 515 | def("ClassMethod"), 516 | def("ClassPrivateMethod"), 517 | def("StaticBlock"), 518 | // Just need to add these types: 519 | def("TSDeclareMethod"), 520 | TSTypeMember 521 | )]); 522 | }; 523 | 524 | maybeSetModuleExports(() => module); 525 | -------------------------------------------------------------------------------- /src/equiv.ts: -------------------------------------------------------------------------------- 1 | import { maybeSetModuleExports } from "./shared"; 2 | import typesPlugin, { Fork } from "./types"; 3 | 4 | export default function (fork: Fork) { 5 | var types = fork.use(typesPlugin); 6 | var getFieldNames = types.getFieldNames; 7 | var getFieldValue = types.getFieldValue; 8 | var isArray = types.builtInTypes.array; 9 | var isObject = types.builtInTypes.object; 10 | var isDate = types.builtInTypes.Date; 11 | var isRegExp = types.builtInTypes.RegExp; 12 | var hasOwn = Object.prototype.hasOwnProperty; 13 | 14 | function astNodesAreEquivalent(a: any, b: any, problemPath?: any) { 15 | if (isArray.check(problemPath)) { 16 | problemPath.length = 0; 17 | } else { 18 | problemPath = null; 19 | } 20 | 21 | return areEquivalent(a, b, problemPath); 22 | } 23 | 24 | astNodesAreEquivalent.assert = function (a: any, b: any) { 25 | var problemPath: any[] = []; 26 | if (!astNodesAreEquivalent(a, b, problemPath)) { 27 | if (problemPath.length === 0) { 28 | if (a !== b) { 29 | throw new Error("Nodes must be equal"); 30 | } 31 | } else { 32 | throw new Error( 33 | "Nodes differ in the following path: " + 34 | problemPath.map(subscriptForProperty).join("") 35 | ); 36 | } 37 | } 38 | }; 39 | 40 | function subscriptForProperty(property: any) { 41 | if (/[_$a-z][_$a-z0-9]*/i.test(property)) { 42 | return "." + property; 43 | } 44 | return "[" + JSON.stringify(property) + "]"; 45 | } 46 | 47 | function areEquivalent(a: any, b: any, problemPath: any) { 48 | if (a === b) { 49 | return true; 50 | } 51 | 52 | if (isArray.check(a)) { 53 | return arraysAreEquivalent(a, b, problemPath); 54 | } 55 | 56 | if (isObject.check(a)) { 57 | return objectsAreEquivalent(a, b, problemPath); 58 | } 59 | 60 | if (isDate.check(a)) { 61 | return isDate.check(b) && (+a === +b); 62 | } 63 | 64 | if (isRegExp.check(a)) { 65 | return isRegExp.check(b) && ( 66 | a.source === b.source && 67 | a.global === b.global && 68 | a.multiline === b.multiline && 69 | a.ignoreCase === b.ignoreCase 70 | ); 71 | } 72 | 73 | return a == b; 74 | } 75 | 76 | function arraysAreEquivalent(a: any, b: any, problemPath: any) { 77 | isArray.assert(a); 78 | var aLength = a.length; 79 | 80 | if (!isArray.check(b) || b.length !== aLength) { 81 | if (problemPath) { 82 | problemPath.push("length"); 83 | } 84 | return false; 85 | } 86 | 87 | for (var i = 0; i < aLength; ++i) { 88 | if (problemPath) { 89 | problemPath.push(i); 90 | } 91 | 92 | if (i in a !== i in b) { 93 | return false; 94 | } 95 | 96 | if (!areEquivalent(a[i], b[i], problemPath)) { 97 | return false; 98 | } 99 | 100 | if (problemPath) { 101 | var problemPathTail = problemPath.pop(); 102 | if (problemPathTail !== i) { 103 | throw new Error("" + problemPathTail); 104 | } 105 | } 106 | } 107 | 108 | return true; 109 | } 110 | 111 | function objectsAreEquivalent(a: any, b: any, problemPath: any) { 112 | isObject.assert(a); 113 | if (!isObject.check(b)) { 114 | return false; 115 | } 116 | 117 | // Fast path for a common property of AST nodes. 118 | if (a.type !== b.type) { 119 | if (problemPath) { 120 | problemPath.push("type"); 121 | } 122 | return false; 123 | } 124 | 125 | var aNames = getFieldNames(a); 126 | var aNameCount = aNames.length; 127 | 128 | var bNames = getFieldNames(b); 129 | var bNameCount = bNames.length; 130 | 131 | if (aNameCount === bNameCount) { 132 | for (var i = 0; i < aNameCount; ++i) { 133 | var name = aNames[i]; 134 | var aChild = getFieldValue(a, name); 135 | var bChild = getFieldValue(b, name); 136 | 137 | if (problemPath) { 138 | problemPath.push(name); 139 | } 140 | 141 | if (!areEquivalent(aChild, bChild, problemPath)) { 142 | return false; 143 | } 144 | 145 | if (problemPath) { 146 | var problemPathTail = problemPath.pop(); 147 | if (problemPathTail !== name) { 148 | throw new Error("" + problemPathTail); 149 | } 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | if (!problemPath) { 157 | return false; 158 | } 159 | 160 | // Since aNameCount !== bNameCount, we need to find some name that's 161 | // missing in aNames but present in bNames, or vice-versa. 162 | 163 | var seenNames = Object.create(null); 164 | 165 | for (i = 0; i < aNameCount; ++i) { 166 | seenNames[aNames[i]] = true; 167 | } 168 | 169 | for (i = 0; i < bNameCount; ++i) { 170 | name = bNames[i]; 171 | 172 | if (!hasOwn.call(seenNames, name)) { 173 | problemPath.push(name); 174 | return false; 175 | } 176 | 177 | delete seenNames[name]; 178 | } 179 | 180 | for (name in seenNames) { 181 | problemPath.push(name); 182 | break; 183 | } 184 | 185 | return false; 186 | } 187 | 188 | return astNodesAreEquivalent; 189 | }; 190 | 191 | maybeSetModuleExports(() => module); 192 | -------------------------------------------------------------------------------- /src/esprima.d.ts: -------------------------------------------------------------------------------- 1 | import * as ESTree from "estree"; 2 | 3 | /** 4 | * "esprima" module augmentations. 5 | */ 6 | declare module "esprima" { 7 | export function parse(input: string, config?: ParseOptions, delegate?: (node: ESTree.Node, meta: any) => void): Program; 8 | } 9 | -------------------------------------------------------------------------------- /src/fork.ts: -------------------------------------------------------------------------------- 1 | import typesPlugin from "./types"; 2 | import pathVisitorPlugin from "./path-visitor"; 3 | import equivPlugin from "./equiv"; 4 | import pathPlugin from "./path"; 5 | import nodePathPlugin from "./node-path"; 6 | import { Fork, Plugin } from "./types"; 7 | import { maybeSetModuleExports } from "./shared"; 8 | 9 | export default function (plugins: Plugin[]) { 10 | const fork = createFork(); 11 | 12 | const types = fork.use(typesPlugin); 13 | 14 | plugins.forEach(fork.use); 15 | 16 | types.finalize(); 17 | 18 | const PathVisitor = fork.use(pathVisitorPlugin); 19 | 20 | return { 21 | Type: types.Type, 22 | builtInTypes: types.builtInTypes, 23 | namedTypes: types.namedTypes, 24 | builders: types.builders, 25 | defineMethod: types.defineMethod, 26 | getFieldNames: types.getFieldNames, 27 | getFieldValue: types.getFieldValue, 28 | eachField: types.eachField, 29 | someField: types.someField, 30 | getSupertypeNames: types.getSupertypeNames, 31 | getBuilderName: types.getBuilderName, 32 | astNodesAreEquivalent: fork.use(equivPlugin), 33 | finalize: types.finalize, 34 | Path: fork.use(pathPlugin), 35 | NodePath: fork.use(nodePathPlugin), 36 | PathVisitor, 37 | use: fork.use, 38 | visit: PathVisitor.visit, 39 | }; 40 | }; 41 | 42 | function createFork(): Fork { 43 | const used: Plugin[] = []; 44 | const usedResult: unknown[] = []; 45 | 46 | function use(plugin: Plugin): T { 47 | var idx = used.indexOf(plugin); 48 | if (idx === -1) { 49 | idx = used.length; 50 | used.push(plugin); 51 | usedResult[idx] = plugin(fork); 52 | } 53 | return usedResult[idx] as T; 54 | } 55 | 56 | var fork: Fork = { use }; 57 | 58 | return fork; 59 | } 60 | 61 | maybeSetModuleExports(() => module); 62 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import fork from "./fork"; 2 | import esProposalsDef from "./def/es-proposals"; 3 | import jsxDef from "./def/jsx"; 4 | import flowDef from "./def/flow"; 5 | import esprimaDef from "./def/esprima"; 6 | import babelDef from "./def/babel"; 7 | import typescriptDef from "./def/typescript"; 8 | import { ASTNode, Type, AnyType, Field } from "./types"; 9 | import { NodePath } from "./node-path"; 10 | import { namedTypes } from "./gen/namedTypes"; 11 | import { builders } from "./gen/builders"; 12 | import { Visitor } from "./gen/visitor"; 13 | 14 | const { 15 | astNodesAreEquivalent, 16 | builders, 17 | builtInTypes, 18 | defineMethod, 19 | eachField, 20 | finalize, 21 | getBuilderName, 22 | getFieldNames, 23 | getFieldValue, 24 | getSupertypeNames, 25 | namedTypes: n, 26 | NodePath, 27 | Path, 28 | PathVisitor, 29 | someField, 30 | Type, 31 | use, 32 | visit, 33 | } = fork([ 34 | // Feel free to add to or remove from this list of extension modules to 35 | // configure the precise type hierarchy that you need. 36 | esProposalsDef, 37 | jsxDef, 38 | flowDef, 39 | esprimaDef, 40 | babelDef, 41 | typescriptDef, 42 | ]); 43 | 44 | // Populate the exported fields of the namedTypes namespace, while still 45 | // retaining its member types. 46 | Object.assign(namedTypes, n); 47 | 48 | export { 49 | AnyType, 50 | ASTNode, 51 | astNodesAreEquivalent, 52 | builders, 53 | builtInTypes, 54 | defineMethod, 55 | eachField, 56 | Field, 57 | finalize, 58 | getBuilderName, 59 | getFieldNames, 60 | getFieldValue, 61 | getSupertypeNames, 62 | namedTypes, 63 | NodePath, 64 | Path, 65 | PathVisitor, 66 | someField, 67 | Type, 68 | use, 69 | visit, 70 | Visitor, 71 | }; 72 | -------------------------------------------------------------------------------- /src/modules.d.ts: -------------------------------------------------------------------------------- 1 | declare module "espree"; 2 | declare module "esprima-fb"; 3 | declare module "flow-parser"; 4 | declare module "recast"; 5 | declare module "reify/*"; 6 | -------------------------------------------------------------------------------- /src/node-path.ts: -------------------------------------------------------------------------------- 1 | import typesPlugin, { ASTNode, Fork } from "./types"; 2 | import pathPlugin, { Path } from "./path"; 3 | import scopePlugin, { Scope } from "./scope"; 4 | import { namedTypes } from "./gen/namedTypes"; 5 | import { maybeSetModuleExports } from "./shared"; 6 | 7 | export interface NodePath extends Path { 8 | node: N; 9 | parent: any; 10 | scope: any; 11 | replace: Path['replace']; 12 | prune(...args: any[]): any; 13 | _computeNode(): any; 14 | _computeParent(): any; 15 | _computeScope(): Scope | null; 16 | getValueProperty(name: any): any; 17 | needsParens(assumeExpressionContext?: boolean): boolean; 18 | canBeFirstInStatement(): boolean; 19 | firstInStatement(): boolean; 20 | } 21 | 22 | export interface NodePathConstructor { 23 | new(value: any, parentPath?: any, name?: any): NodePath; 24 | } 25 | 26 | export default function nodePathPlugin(fork: Fork): NodePathConstructor { 27 | var types = fork.use(typesPlugin); 28 | var n = types.namedTypes; 29 | var b = types.builders; 30 | var isNumber = types.builtInTypes.number; 31 | var isArray = types.builtInTypes.array; 32 | var Path = fork.use(pathPlugin); 33 | var Scope = fork.use(scopePlugin); 34 | 35 | const NodePath = function NodePath(this: NodePath, value: any, parentPath?: any, name?: any) { 36 | if (!(this instanceof NodePath)) { 37 | throw new Error("NodePath constructor cannot be invoked without 'new'"); 38 | } 39 | Path.call(this, value, parentPath, name); 40 | } as any as NodePathConstructor; 41 | 42 | var NPp: NodePath = NodePath.prototype = Object.create(Path.prototype, { 43 | constructor: { 44 | value: NodePath, 45 | enumerable: false, 46 | writable: true, 47 | configurable: true 48 | } 49 | }); 50 | 51 | Object.defineProperties(NPp, { 52 | node: { 53 | get: function () { 54 | Object.defineProperty(this, "node", { 55 | configurable: true, // Enable deletion. 56 | value: this._computeNode() 57 | }); 58 | 59 | return this.node; 60 | } 61 | }, 62 | 63 | parent: { 64 | get: function () { 65 | Object.defineProperty(this, "parent", { 66 | configurable: true, // Enable deletion. 67 | value: this._computeParent() 68 | }); 69 | 70 | return this.parent; 71 | } 72 | }, 73 | 74 | scope: { 75 | get: function () { 76 | Object.defineProperty(this, "scope", { 77 | configurable: true, // Enable deletion. 78 | value: this._computeScope() 79 | }); 80 | 81 | return this.scope; 82 | } 83 | } 84 | }); 85 | 86 | NPp.replace = function () { 87 | delete this.node; 88 | delete this.parent; 89 | delete this.scope; 90 | return Path.prototype.replace.apply(this, arguments); 91 | }; 92 | 93 | NPp.prune = function () { 94 | var remainingNodePath = this.parent; 95 | 96 | this.replace(); 97 | 98 | return cleanUpNodesAfterPrune(remainingNodePath); 99 | }; 100 | 101 | // The value of the first ancestor Path whose value is a Node. 102 | NPp._computeNode = function () { 103 | var value = this.value; 104 | if (n.Node.check(value)) { 105 | return value; 106 | } 107 | 108 | var pp = this.parentPath; 109 | return pp && pp.node || null; 110 | }; 111 | 112 | // The first ancestor Path whose value is a Node distinct from this.node. 113 | NPp._computeParent = function () { 114 | var value = this.value; 115 | var pp = this.parentPath; 116 | 117 | if (!n.Node.check(value)) { 118 | while (pp && !n.Node.check(pp.value)) { 119 | pp = pp.parentPath; 120 | } 121 | 122 | if (pp) { 123 | pp = pp.parentPath; 124 | } 125 | } 126 | 127 | while (pp && !n.Node.check(pp.value)) { 128 | pp = pp.parentPath; 129 | } 130 | 131 | return pp || null; 132 | }; 133 | 134 | // The closest enclosing scope that governs this node. 135 | NPp._computeScope = function () { 136 | var value = this.value; 137 | var pp = this.parentPath; 138 | var scope = pp && pp.scope; 139 | 140 | if (n.Node.check(value) && 141 | Scope.isEstablishedBy(value)) { 142 | scope = new Scope(this, scope); 143 | } 144 | 145 | return scope || null; 146 | }; 147 | 148 | NPp.getValueProperty = function (name) { 149 | return types.getFieldValue(this.value, name); 150 | }; 151 | 152 | /** 153 | * Determine whether this.node needs to be wrapped in parentheses in order 154 | * for a parser to reproduce the same local AST structure. 155 | * 156 | * For instance, in the expression `(1 + 2) * 3`, the BinaryExpression 157 | * whose operator is "+" needs parentheses, because `1 + 2 * 3` would 158 | * parse differently. 159 | * 160 | * If assumeExpressionContext === true, we don't worry about edge cases 161 | * like an anonymous FunctionExpression appearing lexically first in its 162 | * enclosing statement and thus needing parentheses to avoid being parsed 163 | * as a FunctionDeclaration with a missing name. 164 | */ 165 | NPp.needsParens = function (assumeExpressionContext) { 166 | var pp = this.parentPath; 167 | if (!pp) { 168 | return false; 169 | } 170 | 171 | var node = this.value; 172 | 173 | // Only expressions need parentheses. 174 | if (!n.Expression.check(node)) { 175 | return false; 176 | } 177 | 178 | // Identifiers never need parentheses. 179 | if (node.type === "Identifier") { 180 | return false; 181 | } 182 | 183 | while (!n.Node.check(pp.value)) { 184 | pp = pp.parentPath; 185 | if (!pp) { 186 | return false; 187 | } 188 | } 189 | 190 | var parent = pp.value; 191 | 192 | switch (node.type) { 193 | case "UnaryExpression": 194 | case "SpreadElement": 195 | case "SpreadProperty": 196 | return parent.type === "MemberExpression" 197 | && this.name === "object" 198 | && parent.object === node; 199 | 200 | case "BinaryExpression": 201 | case "LogicalExpression": 202 | switch (parent.type) { 203 | case "CallExpression": 204 | return this.name === "callee" 205 | && parent.callee === node; 206 | 207 | case "UnaryExpression": 208 | case "SpreadElement": 209 | case "SpreadProperty": 210 | return true; 211 | 212 | case "MemberExpression": 213 | return this.name === "object" 214 | && parent.object === node; 215 | 216 | case "BinaryExpression": 217 | case "LogicalExpression": { 218 | const n = node as namedTypes.BinaryExpression | namedTypes.LogicalExpression; 219 | const po = parent.operator; 220 | const pp = PRECEDENCE[po]; 221 | const no = n.operator; 222 | const np = PRECEDENCE[no]; 223 | 224 | if (pp > np) { 225 | return true; 226 | } 227 | 228 | if (pp === np && this.name === "right") { 229 | if (parent.right !== n) { 230 | throw new Error("Nodes must be equal"); 231 | } 232 | return true; 233 | } 234 | } 235 | 236 | default: 237 | return false; 238 | } 239 | 240 | case "SequenceExpression": 241 | switch (parent.type) { 242 | case "ForStatement": 243 | // Although parentheses wouldn't hurt around sequence 244 | // expressions in the head of for loops, traditional style 245 | // dictates that e.g. i++, j++ should not be wrapped with 246 | // parentheses. 247 | return false; 248 | 249 | case "ExpressionStatement": 250 | return this.name !== "expression"; 251 | 252 | default: 253 | // Otherwise err on the side of overparenthesization, adding 254 | // explicit exceptions above if this proves overzealous. 255 | return true; 256 | } 257 | 258 | case "YieldExpression": 259 | switch (parent.type) { 260 | case "BinaryExpression": 261 | case "LogicalExpression": 262 | case "UnaryExpression": 263 | case "SpreadElement": 264 | case "SpreadProperty": 265 | case "CallExpression": 266 | case "MemberExpression": 267 | case "NewExpression": 268 | case "ConditionalExpression": 269 | case "YieldExpression": 270 | return true; 271 | 272 | default: 273 | return false; 274 | } 275 | 276 | case "Literal": 277 | return parent.type === "MemberExpression" 278 | && isNumber.check((node as namedTypes.Literal).value) 279 | && this.name === "object" 280 | && parent.object === node; 281 | 282 | case "AssignmentExpression": 283 | case "ConditionalExpression": 284 | switch (parent.type) { 285 | case "UnaryExpression": 286 | case "SpreadElement": 287 | case "SpreadProperty": 288 | case "BinaryExpression": 289 | case "LogicalExpression": 290 | return true; 291 | 292 | case "CallExpression": 293 | return this.name === "callee" 294 | && parent.callee === node; 295 | 296 | case "ConditionalExpression": 297 | return this.name === "test" 298 | && parent.test === node; 299 | 300 | case "MemberExpression": 301 | return this.name === "object" 302 | && parent.object === node; 303 | 304 | default: 305 | return false; 306 | } 307 | 308 | default: 309 | if (parent.type === "NewExpression" && 310 | this.name === "callee" && 311 | parent.callee === node) { 312 | return containsCallExpression(node); 313 | } 314 | } 315 | 316 | if (assumeExpressionContext !== true && 317 | !this.canBeFirstInStatement() && 318 | this.firstInStatement()) 319 | return true; 320 | 321 | return false; 322 | }; 323 | 324 | function isBinary(node: any) { 325 | return n.BinaryExpression.check(node) 326 | || n.LogicalExpression.check(node); 327 | } 328 | 329 | // @ts-ignore 'isUnaryLike' is declared but its value is never read. [6133] 330 | function isUnaryLike(node: any) { 331 | return n.UnaryExpression.check(node) 332 | // I considered making SpreadElement and SpreadProperty subtypes 333 | // of UnaryExpression, but they're not really Expression nodes. 334 | || (n.SpreadElement && n.SpreadElement.check(node)) 335 | || (n.SpreadProperty && n.SpreadProperty.check(node)); 336 | } 337 | 338 | var PRECEDENCE: any = {}; 339 | [["||"], 340 | ["&&"], 341 | ["|"], 342 | ["^"], 343 | ["&"], 344 | ["==", "===", "!=", "!=="], 345 | ["<", ">", "<=", ">=", "in", "instanceof"], 346 | [">>", "<<", ">>>"], 347 | ["+", "-"], 348 | ["*", "/", "%"] 349 | ].forEach(function (tier, i) { 350 | tier.forEach(function (op) { 351 | PRECEDENCE[op] = i; 352 | }); 353 | }); 354 | 355 | function containsCallExpression(node: any): any { 356 | if (n.CallExpression.check(node)) { 357 | return true; 358 | } 359 | 360 | if (isArray.check(node)) { 361 | return node.some(containsCallExpression); 362 | } 363 | 364 | if (n.Node.check(node)) { 365 | return types.someField(node, function (_name: any, child: any) { 366 | return containsCallExpression(child); 367 | }); 368 | } 369 | 370 | return false; 371 | } 372 | 373 | NPp.canBeFirstInStatement = function () { 374 | var node = this.node; 375 | return !n.FunctionExpression.check(node) 376 | && !n.ObjectExpression.check(node); 377 | }; 378 | 379 | NPp.firstInStatement = function () { 380 | return firstInStatement(this); 381 | }; 382 | 383 | function firstInStatement(path: any) { 384 | for (var node, parent; path.parent; path = path.parent) { 385 | node = path.node; 386 | parent = path.parent.node; 387 | 388 | if (n.BlockStatement.check(parent) && 389 | path.parent.name === "body" && 390 | path.name === 0) { 391 | if (parent.body[0] !== node) { 392 | throw new Error("Nodes must be equal"); 393 | } 394 | return true; 395 | } 396 | 397 | if (n.ExpressionStatement.check(parent) && 398 | path.name === "expression") { 399 | if (parent.expression !== node) { 400 | throw new Error("Nodes must be equal"); 401 | } 402 | return true; 403 | } 404 | 405 | if (n.SequenceExpression.check(parent) && 406 | path.parent.name === "expressions" && 407 | path.name === 0) { 408 | if (parent.expressions[0] !== node) { 409 | throw new Error("Nodes must be equal"); 410 | } 411 | continue; 412 | } 413 | 414 | if (n.CallExpression.check(parent) && 415 | path.name === "callee") { 416 | if (parent.callee !== node) { 417 | throw new Error("Nodes must be equal"); 418 | } 419 | continue; 420 | } 421 | 422 | if (n.MemberExpression.check(parent) && 423 | path.name === "object") { 424 | if (parent.object !== node) { 425 | throw new Error("Nodes must be equal"); 426 | } 427 | continue; 428 | } 429 | 430 | if (n.ConditionalExpression.check(parent) && 431 | path.name === "test") { 432 | if (parent.test !== node) { 433 | throw new Error("Nodes must be equal"); 434 | } 435 | continue; 436 | } 437 | 438 | if (isBinary(parent) && 439 | path.name === "left") { 440 | if (parent.left !== node) { 441 | throw new Error("Nodes must be equal"); 442 | } 443 | continue; 444 | } 445 | 446 | if (n.UnaryExpression.check(parent) && 447 | !parent.prefix && 448 | path.name === "argument") { 449 | if (parent.argument !== node) { 450 | throw new Error("Nodes must be equal"); 451 | } 452 | continue; 453 | } 454 | 455 | return false; 456 | } 457 | 458 | return true; 459 | } 460 | 461 | /** 462 | * Pruning certain nodes will result in empty or incomplete nodes, here we clean those nodes up. 463 | */ 464 | function cleanUpNodesAfterPrune(remainingNodePath: any) { 465 | if (n.VariableDeclaration.check(remainingNodePath.node)) { 466 | var declarations = remainingNodePath.get('declarations').value; 467 | if (!declarations || declarations.length === 0) { 468 | return remainingNodePath.prune(); 469 | } 470 | } else if (n.ExpressionStatement.check(remainingNodePath.node)) { 471 | if (!remainingNodePath.get('expression').value) { 472 | return remainingNodePath.prune(); 473 | } 474 | } else if (n.IfStatement.check(remainingNodePath.node)) { 475 | cleanUpIfStatementAfterPrune(remainingNodePath); 476 | } 477 | 478 | return remainingNodePath; 479 | } 480 | 481 | function cleanUpIfStatementAfterPrune(ifStatement: any) { 482 | var testExpression = ifStatement.get('test').value; 483 | var alternate = ifStatement.get('alternate').value; 484 | var consequent = ifStatement.get('consequent').value; 485 | 486 | if (!consequent && !alternate) { 487 | var testExpressionStatement = b.expressionStatement(testExpression); 488 | 489 | ifStatement.replace(testExpressionStatement); 490 | } else if (!consequent && alternate) { 491 | var negatedTestExpression: namedTypes.Expression = 492 | b.unaryExpression('!', testExpression, true); 493 | 494 | if (n.UnaryExpression.check(testExpression) && testExpression.operator === '!') { 495 | negatedTestExpression = testExpression.argument; 496 | } 497 | 498 | ifStatement.get("test").replace(negatedTestExpression); 499 | ifStatement.get("consequent").replace(alternate); 500 | ifStatement.get("alternate").replace(); 501 | } 502 | } 503 | 504 | return NodePath; 505 | }; 506 | 507 | maybeSetModuleExports(() => module); 508 | -------------------------------------------------------------------------------- /src/path-visitor.ts: -------------------------------------------------------------------------------- 1 | import typesPlugin, { ASTNode, Fork, Omit } from "./types"; 2 | import nodePathPlugin, { NodePath } from "./node-path"; 3 | import { maybeSetModuleExports } from "./shared"; 4 | 5 | var hasOwn = Object.prototype.hasOwnProperty; 6 | 7 | export interface PathVisitor { 8 | _reusableContextStack: any; 9 | _methodNameTable: any; 10 | _shouldVisitComments: any; 11 | Context: any; 12 | _visiting: any; 13 | _changeReported: any; 14 | _abortRequested: boolean; 15 | visit(...args: any[]): any; 16 | reset(...args: any[]): any; 17 | visitWithoutReset(path: any): any; 18 | AbortRequest: any; 19 | abort(): void; 20 | visitor: any; 21 | acquireContext(path: any): any; 22 | releaseContext(context: any): void; 23 | reportChanged(): void; 24 | wasChangeReported(): any; 25 | } 26 | 27 | export interface PathVisitorStatics { 28 | fromMethodsObject(methods?: any): Visitor; 29 | visit(node: ASTNode, methods?: import("./gen/visitor").Visitor): any; 30 | } 31 | 32 | export interface PathVisitorConstructor extends PathVisitorStatics { 33 | new(): PathVisitor; 34 | } 35 | 36 | export interface Visitor extends PathVisitor {} 37 | 38 | export interface VisitorConstructor extends PathVisitorStatics { 39 | new(): Visitor; 40 | } 41 | 42 | export interface VisitorMethods { 43 | [visitorMethod: string]: (path: NodePath) => any; 44 | } 45 | 46 | export interface SharedContextMethods { 47 | currentPath: any; 48 | needToCallTraverse: boolean; 49 | Context: any; 50 | visitor: any; 51 | reset(path: any, ...args: any[]): any; 52 | invokeVisitorMethod(methodName: string): any; 53 | traverse(path: any, newVisitor?: VisitorMethods): any; 54 | visit(path: any, newVisitor?: VisitorMethods): any; 55 | reportChanged(): void; 56 | abort(): void; 57 | } 58 | 59 | export interface Context extends Omit, SharedContextMethods {} 60 | 61 | export default function pathVisitorPlugin(fork: Fork) { 62 | var types = fork.use(typesPlugin); 63 | var NodePath = fork.use(nodePathPlugin); 64 | var isArray = types.builtInTypes.array; 65 | var isObject = types.builtInTypes.object; 66 | var isFunction = types.builtInTypes.function; 67 | var undefined: any; 68 | 69 | const PathVisitor = function PathVisitor(this: PathVisitor) { 70 | if (!(this instanceof PathVisitor)) { 71 | throw new Error( 72 | "PathVisitor constructor cannot be invoked without 'new'" 73 | ); 74 | } 75 | 76 | // Permanent state. 77 | this._reusableContextStack = []; 78 | 79 | this._methodNameTable = computeMethodNameTable(this); 80 | this._shouldVisitComments = 81 | hasOwn.call(this._methodNameTable, "Block") || 82 | hasOwn.call(this._methodNameTable, "Line"); 83 | 84 | this.Context = makeContextConstructor(this); 85 | 86 | // State reset every time PathVisitor.prototype.visit is called. 87 | this._visiting = false; 88 | this._changeReported = false; 89 | } as any as PathVisitorConstructor; 90 | 91 | function computeMethodNameTable(visitor: any) { 92 | var typeNames = Object.create(null); 93 | 94 | for (var methodName in visitor) { 95 | if (/^visit[A-Z]/.test(methodName)) { 96 | typeNames[methodName.slice("visit".length)] = true; 97 | } 98 | } 99 | 100 | var supertypeTable = types.computeSupertypeLookupTable(typeNames); 101 | var methodNameTable = Object.create(null); 102 | 103 | var typeNameKeys = Object.keys(supertypeTable); 104 | var typeNameCount = typeNameKeys.length; 105 | for (var i = 0; i < typeNameCount; ++i) { 106 | var typeName = typeNameKeys[i]; 107 | methodName = "visit" + supertypeTable[typeName]; 108 | if (isFunction.check(visitor[methodName])) { 109 | methodNameTable[typeName] = methodName; 110 | } 111 | } 112 | 113 | return methodNameTable; 114 | } 115 | 116 | PathVisitor.fromMethodsObject = function fromMethodsObject(methods) { 117 | if (methods instanceof PathVisitor) { 118 | return methods; 119 | } 120 | 121 | if (!isObject.check(methods)) { 122 | // An empty visitor? 123 | return new PathVisitor; 124 | } 125 | 126 | const Visitor = function Visitor(this: any) { 127 | if (!(this instanceof Visitor)) { 128 | throw new Error( 129 | "Visitor constructor cannot be invoked without 'new'" 130 | ); 131 | } 132 | PathVisitor.call(this); 133 | } as any as VisitorConstructor; 134 | 135 | var Vp = Visitor.prototype = Object.create(PVp); 136 | Vp.constructor = Visitor; 137 | 138 | extend(Vp, methods); 139 | extend(Visitor, PathVisitor); 140 | 141 | isFunction.assert(Visitor.fromMethodsObject); 142 | isFunction.assert(Visitor.visit); 143 | 144 | return new Visitor; 145 | }; 146 | 147 | function extend(target: any, source: any) { 148 | for (var property in source) { 149 | if (hasOwn.call(source, property)) { 150 | target[property] = source[property]; 151 | } 152 | } 153 | 154 | return target; 155 | } 156 | 157 | PathVisitor.visit = function visit(node, methods) { 158 | return PathVisitor.fromMethodsObject(methods).visit(node); 159 | }; 160 | 161 | var PVp: PathVisitor = PathVisitor.prototype; 162 | 163 | PVp.visit = function () { 164 | if (this._visiting) { 165 | throw new Error( 166 | "Recursively calling visitor.visit(path) resets visitor state. " + 167 | "Try this.visit(path) or this.traverse(path) instead." 168 | ); 169 | } 170 | 171 | // Private state that needs to be reset before every traversal. 172 | this._visiting = true; 173 | this._changeReported = false; 174 | this._abortRequested = false; 175 | 176 | var argc = arguments.length; 177 | var args = new Array(argc) 178 | for (var i = 0; i < argc; ++i) { 179 | args[i] = arguments[i]; 180 | } 181 | 182 | if (!(args[0] instanceof NodePath)) { 183 | args[0] = new NodePath({root: args[0]}).get("root"); 184 | } 185 | 186 | // Called with the same arguments as .visit. 187 | this.reset.apply(this, args); 188 | 189 | var didNotThrow; 190 | try { 191 | var root = this.visitWithoutReset(args[0]); 192 | didNotThrow = true; 193 | } finally { 194 | this._visiting = false; 195 | 196 | if (!didNotThrow && this._abortRequested) { 197 | // If this.visitWithoutReset threw an exception and 198 | // this._abortRequested was set to true, return the root of 199 | // the AST instead of letting the exception propagate, so that 200 | // client code does not have to provide a try-catch block to 201 | // intercept the AbortRequest exception. Other kinds of 202 | // exceptions will propagate without being intercepted and 203 | // rethrown by a catch block, so their stacks will accurately 204 | // reflect the original throwing context. 205 | return args[0].value; 206 | } 207 | } 208 | 209 | return root; 210 | }; 211 | 212 | PVp.AbortRequest = function AbortRequest() {}; 213 | PVp.abort = function () { 214 | var visitor = this; 215 | visitor._abortRequested = true; 216 | var request = new visitor.AbortRequest(); 217 | 218 | // If you decide to catch this exception and stop it from propagating, 219 | // make sure to call its cancel method to avoid silencing other 220 | // exceptions that might be thrown later in the traversal. 221 | request.cancel = function () { 222 | visitor._abortRequested = false; 223 | }; 224 | 225 | throw request; 226 | }; 227 | 228 | PVp.reset = function (_path/*, additional arguments */) { 229 | // Empty stub; may be reassigned or overridden by subclasses. 230 | }; 231 | 232 | PVp.visitWithoutReset = function (path) { 233 | if (this instanceof this.Context) { 234 | // Since this.Context.prototype === this, there's a chance we 235 | // might accidentally call context.visitWithoutReset. If that 236 | // happens, re-invoke the method against context.visitor. 237 | return this.visitor.visitWithoutReset(path); 238 | } 239 | 240 | if (!(path instanceof NodePath)) { 241 | throw new Error(""); 242 | } 243 | 244 | var value = path.value; 245 | 246 | var methodName = value && 247 | typeof value === "object" && 248 | typeof value.type === "string" && 249 | this._methodNameTable[value.type]; 250 | 251 | if (methodName) { 252 | var context = this.acquireContext(path); 253 | try { 254 | return context.invokeVisitorMethod(methodName); 255 | } finally { 256 | this.releaseContext(context); 257 | } 258 | 259 | } else { 260 | // If there was no visitor method to call, visit the children of 261 | // this node generically. 262 | return visitChildren(path, this); 263 | } 264 | }; 265 | 266 | function visitChildren(path: any, visitor: any) { 267 | if (!(path instanceof NodePath)) { 268 | throw new Error(""); 269 | } 270 | if (!(visitor instanceof PathVisitor)) { 271 | throw new Error(""); 272 | } 273 | 274 | var value = path.value; 275 | 276 | if (isArray.check(value)) { 277 | path.each(visitor.visitWithoutReset, visitor); 278 | } else if (!isObject.check(value)) { 279 | // No children to visit. 280 | } else { 281 | var childNames = types.getFieldNames(value); 282 | 283 | // The .comments field of the Node type is hidden, so we only 284 | // visit it if the visitor defines visitBlock or visitLine, and 285 | // value.comments is defined. 286 | if (visitor._shouldVisitComments && 287 | value.comments && 288 | childNames.indexOf("comments") < 0) { 289 | childNames.push("comments"); 290 | } 291 | 292 | var childCount = childNames.length; 293 | var childPaths = []; 294 | 295 | for (var i = 0; i < childCount; ++i) { 296 | var childName = childNames[i]; 297 | if (!hasOwn.call(value, childName)) { 298 | value[childName] = types.getFieldValue(value, childName); 299 | } 300 | childPaths.push(path.get(childName)); 301 | } 302 | 303 | for (var i = 0; i < childCount; ++i) { 304 | visitor.visitWithoutReset(childPaths[i]); 305 | } 306 | } 307 | 308 | return path.value; 309 | } 310 | 311 | PVp.acquireContext = function (path) { 312 | if (this._reusableContextStack.length === 0) { 313 | return new this.Context(path); 314 | } 315 | return this._reusableContextStack.pop().reset(path); 316 | }; 317 | 318 | PVp.releaseContext = function (context) { 319 | if (!(context instanceof this.Context)) { 320 | throw new Error(""); 321 | } 322 | this._reusableContextStack.push(context); 323 | context.currentPath = null; 324 | }; 325 | 326 | PVp.reportChanged = function () { 327 | this._changeReported = true; 328 | }; 329 | 330 | PVp.wasChangeReported = function () { 331 | return this._changeReported; 332 | }; 333 | 334 | function makeContextConstructor(visitor: any) { 335 | function Context(this: Context, path: any) { 336 | if (!(this instanceof Context)) { 337 | throw new Error(""); 338 | } 339 | if (!(this instanceof PathVisitor)) { 340 | throw new Error(""); 341 | } 342 | if (!(path instanceof NodePath)) { 343 | throw new Error(""); 344 | } 345 | 346 | Object.defineProperty(this, "visitor", { 347 | value: visitor, 348 | writable: false, 349 | enumerable: true, 350 | configurable: false 351 | }); 352 | 353 | this.currentPath = path; 354 | this.needToCallTraverse = true; 355 | 356 | Object.seal(this); 357 | } 358 | 359 | if (!(visitor instanceof PathVisitor)) { 360 | throw new Error(""); 361 | } 362 | 363 | // Note that the visitor object is the prototype of Context.prototype, 364 | // so all visitor methods are inherited by context objects. 365 | var Cp = Context.prototype = Object.create(visitor); 366 | 367 | Cp.constructor = Context; 368 | extend(Cp, sharedContextProtoMethods); 369 | 370 | return Context; 371 | } 372 | 373 | // Every PathVisitor has a different this.Context constructor and 374 | // this.Context.prototype object, but those prototypes can all use the 375 | // same reset, invokeVisitorMethod, and traverse function objects. 376 | var sharedContextProtoMethods: SharedContextMethods = Object.create(null); 377 | 378 | sharedContextProtoMethods.reset = 379 | function reset(path) { 380 | if (!(this instanceof this.Context)) { 381 | throw new Error(""); 382 | } 383 | if (!(path instanceof NodePath)) { 384 | throw new Error(""); 385 | } 386 | 387 | this.currentPath = path; 388 | this.needToCallTraverse = true; 389 | 390 | return this; 391 | }; 392 | 393 | sharedContextProtoMethods.invokeVisitorMethod = 394 | function invokeVisitorMethod(methodName) { 395 | if (!(this instanceof this.Context)) { 396 | throw new Error(""); 397 | } 398 | if (!(this.currentPath instanceof NodePath)) { 399 | throw new Error(""); 400 | } 401 | 402 | var result = this.visitor[methodName].call(this, this.currentPath); 403 | 404 | if (result === false) { 405 | // Visitor methods return false to indicate that they have handled 406 | // their own traversal needs, and we should not complain if 407 | // this.needToCallTraverse is still true. 408 | this.needToCallTraverse = false; 409 | 410 | } else if (result !== undefined) { 411 | // Any other non-undefined value returned from the visitor method 412 | // is interpreted as a replacement value. 413 | this.currentPath = this.currentPath.replace(result)[0]; 414 | 415 | if (this.needToCallTraverse) { 416 | // If this.traverse still hasn't been called, visit the 417 | // children of the replacement node. 418 | this.traverse(this.currentPath); 419 | } 420 | } 421 | 422 | if (this.needToCallTraverse !== false) { 423 | throw new Error( 424 | "Must either call this.traverse or return false in " + methodName 425 | ); 426 | } 427 | 428 | var path = this.currentPath; 429 | return path && path.value; 430 | }; 431 | 432 | sharedContextProtoMethods.traverse = 433 | function traverse(path, newVisitor) { 434 | if (!(this instanceof this.Context)) { 435 | throw new Error(""); 436 | } 437 | if (!(path instanceof NodePath)) { 438 | throw new Error(""); 439 | } 440 | if (!(this.currentPath instanceof NodePath)) { 441 | throw new Error(""); 442 | } 443 | 444 | this.needToCallTraverse = false; 445 | 446 | return visitChildren(path, PathVisitor.fromMethodsObject( 447 | newVisitor || this.visitor 448 | )); 449 | }; 450 | 451 | sharedContextProtoMethods.visit = 452 | function visit(path, newVisitor) { 453 | if (!(this instanceof this.Context)) { 454 | throw new Error(""); 455 | } 456 | if (!(path instanceof NodePath)) { 457 | throw new Error(""); 458 | } 459 | if (!(this.currentPath instanceof NodePath)) { 460 | throw new Error(""); 461 | } 462 | 463 | this.needToCallTraverse = false; 464 | 465 | return PathVisitor.fromMethodsObject( 466 | newVisitor || this.visitor 467 | ).visitWithoutReset(path); 468 | }; 469 | 470 | sharedContextProtoMethods.reportChanged = function reportChanged() { 471 | this.visitor.reportChanged(); 472 | }; 473 | 474 | sharedContextProtoMethods.abort = function abort() { 475 | this.needToCallTraverse = false; 476 | this.visitor.abort(); 477 | }; 478 | 479 | return PathVisitor; 480 | }; 481 | 482 | maybeSetModuleExports(() => module); 483 | -------------------------------------------------------------------------------- /src/path.ts: -------------------------------------------------------------------------------- 1 | import { maybeSetModuleExports } from "./shared"; 2 | import typesPlugin, { ASTNode, Fork } from "./types"; 3 | 4 | var Op = Object.prototype; 5 | var hasOwn = Op.hasOwnProperty; 6 | 7 | export interface Path { 8 | value: V; 9 | parentPath: any; 10 | name: any; 11 | __childCache: object | null; 12 | getValueProperty(name: any): any; 13 | get(...names: any[]): any; 14 | each(callback: any, context?: any): any; 15 | map(callback: any, context?: any): any; 16 | filter(callback: any, context?: any): any; 17 | shift(): any; 18 | unshift(...args: any[]): any; 19 | push(...args: any[]): any; 20 | pop(): any; 21 | insertAt(index: number, ...args: any[]): any; 22 | insertBefore(...args: any[]): any; 23 | insertAfter(...args: any[]): any; 24 | replace(replacement?: ASTNode, ...args: ASTNode[]): any; 25 | } 26 | 27 | export interface PathConstructor { 28 | new(value: any, parentPath?: any, name?: any): Path; 29 | } 30 | 31 | export default function pathPlugin(fork: Fork): PathConstructor { 32 | var types = fork.use(typesPlugin); 33 | var isArray = types.builtInTypes.array; 34 | var isNumber = types.builtInTypes.number; 35 | 36 | const Path = function Path(this: Path, value: any, parentPath?: any, name?: any) { 37 | if (!(this instanceof Path)) { 38 | throw new Error("Path constructor cannot be invoked without 'new'"); 39 | } 40 | 41 | if (parentPath) { 42 | if (!(parentPath instanceof Path)) { 43 | throw new Error(""); 44 | } 45 | } else { 46 | parentPath = null; 47 | name = null; 48 | } 49 | 50 | // The value encapsulated by this Path, generally equal to 51 | // parentPath.value[name] if we have a parentPath. 52 | this.value = value; 53 | 54 | // The immediate parent Path of this Path. 55 | this.parentPath = parentPath; 56 | 57 | // The name of the property of parentPath.value through which this 58 | // Path's value was reached. 59 | this.name = name; 60 | 61 | // Calling path.get("child") multiple times always returns the same 62 | // child Path object, for both performance and consistency reasons. 63 | this.__childCache = null; 64 | } as any as PathConstructor; 65 | 66 | var Pp: Path = Path.prototype; 67 | 68 | function getChildCache(path: any) { 69 | // Lazily create the child cache. This also cheapens cache 70 | // invalidation, since you can just reset path.__childCache to null. 71 | return path.__childCache || (path.__childCache = Object.create(null)); 72 | } 73 | 74 | function getChildPath(path: any, name: any) { 75 | var cache = getChildCache(path); 76 | var actualChildValue = path.getValueProperty(name); 77 | var childPath = cache[name]; 78 | if (!hasOwn.call(cache, name) || 79 | // Ensure consistency between cache and reality. 80 | childPath.value !== actualChildValue) { 81 | childPath = cache[name] = new path.constructor( 82 | actualChildValue, path, name 83 | ); 84 | } 85 | return childPath; 86 | } 87 | 88 | // This method is designed to be overridden by subclasses that need to 89 | // handle missing properties, etc. 90 | Pp.getValueProperty = function getValueProperty(name) { 91 | return this.value[name]; 92 | }; 93 | 94 | Pp.get = function get(...names) { 95 | var path = this; 96 | var count = names.length; 97 | 98 | for (var i = 0; i < count; ++i) { 99 | path = getChildPath(path, names[i]); 100 | } 101 | 102 | return path; 103 | }; 104 | 105 | Pp.each = function each(callback, context) { 106 | var childPaths = []; 107 | var len = this.value.length; 108 | var i = 0; 109 | 110 | // Collect all the original child paths before invoking the callback. 111 | for (var i = 0; i < len; ++i) { 112 | if (hasOwn.call(this.value, i)) { 113 | childPaths[i] = this.get(i); 114 | } 115 | } 116 | 117 | // Invoke the callback on just the original child paths, regardless of 118 | // any modifications made to the array by the callback. I chose these 119 | // semantics over cleverly invoking the callback on new elements because 120 | // this way is much easier to reason about. 121 | context = context || this; 122 | for (i = 0; i < len; ++i) { 123 | if (hasOwn.call(childPaths, i)) { 124 | callback.call(context, childPaths[i]); 125 | } 126 | } 127 | }; 128 | 129 | Pp.map = function map(callback, context) { 130 | var result: any[] = []; 131 | 132 | this.each(function (this: any, childPath: any) { 133 | result.push(callback.call(this, childPath)); 134 | }, context); 135 | 136 | return result; 137 | }; 138 | 139 | Pp.filter = function filter(callback, context) { 140 | var result: any[] = []; 141 | 142 | this.each(function (this: any, childPath: any) { 143 | if (callback.call(this, childPath)) { 144 | result.push(childPath); 145 | } 146 | }, context); 147 | 148 | return result; 149 | }; 150 | 151 | function emptyMoves() {} 152 | function getMoves(path: any, offset: number, start?: any, end?: any) { 153 | isArray.assert(path.value); 154 | 155 | if (offset === 0) { 156 | return emptyMoves; 157 | } 158 | 159 | var length = path.value.length; 160 | if (length < 1) { 161 | return emptyMoves; 162 | } 163 | 164 | var argc = arguments.length; 165 | if (argc === 2) { 166 | start = 0; 167 | end = length; 168 | } else if (argc === 3) { 169 | start = Math.max(start, 0); 170 | end = length; 171 | } else { 172 | start = Math.max(start, 0); 173 | end = Math.min(end, length); 174 | } 175 | 176 | isNumber.assert(start); 177 | isNumber.assert(end); 178 | 179 | var moves = Object.create(null); 180 | var cache = getChildCache(path); 181 | 182 | for (var i = start; i < end; ++i) { 183 | if (hasOwn.call(path.value, i)) { 184 | var childPath = path.get(i); 185 | if (childPath.name !== i) { 186 | throw new Error(""); 187 | } 188 | var newIndex = i + offset; 189 | childPath.name = newIndex; 190 | moves[newIndex] = childPath; 191 | delete cache[i]; 192 | } 193 | } 194 | 195 | delete cache.length; 196 | 197 | return function () { 198 | for (var newIndex in moves) { 199 | var childPath = moves[newIndex]; 200 | if (childPath.name !== +newIndex) { 201 | throw new Error(""); 202 | } 203 | cache[newIndex] = childPath; 204 | path.value[newIndex] = childPath.value; 205 | } 206 | }; 207 | } 208 | 209 | Pp.shift = function shift() { 210 | var move = getMoves(this, -1); 211 | var result = this.value.shift(); 212 | move(); 213 | return result; 214 | }; 215 | 216 | Pp.unshift = function unshift(...args) { 217 | var move = getMoves(this, args.length); 218 | var result = this.value.unshift.apply(this.value, args); 219 | move(); 220 | return result; 221 | }; 222 | 223 | Pp.push = function push(...args) { 224 | isArray.assert(this.value); 225 | delete getChildCache(this).length 226 | return this.value.push.apply(this.value, args); 227 | }; 228 | 229 | Pp.pop = function pop() { 230 | isArray.assert(this.value); 231 | var cache = getChildCache(this); 232 | delete cache[this.value.length - 1]; 233 | delete cache.length; 234 | return this.value.pop(); 235 | }; 236 | 237 | Pp.insertAt = function insertAt(index) { 238 | var argc = arguments.length; 239 | var move = getMoves(this, argc - 1, index); 240 | if (move === emptyMoves && argc <= 1) { 241 | return this; 242 | } 243 | 244 | index = Math.max(index, 0); 245 | 246 | for (var i = 1; i < argc; ++i) { 247 | this.value[index + i - 1] = arguments[i]; 248 | } 249 | 250 | move(); 251 | 252 | return this; 253 | }; 254 | 255 | Pp.insertBefore = function insertBefore(...args) { 256 | var pp = this.parentPath; 257 | var argc = args.length; 258 | var insertAtArgs = [this.name]; 259 | for (var i = 0; i < argc; ++i) { 260 | insertAtArgs.push(args[i]); 261 | } 262 | return pp.insertAt.apply(pp, insertAtArgs); 263 | }; 264 | 265 | Pp.insertAfter = function insertAfter(...args) { 266 | var pp = this.parentPath; 267 | var argc = args.length; 268 | var insertAtArgs = [this.name + 1]; 269 | for (var i = 0; i < argc; ++i) { 270 | insertAtArgs.push(args[i]); 271 | } 272 | return pp.insertAt.apply(pp, insertAtArgs); 273 | }; 274 | 275 | function repairRelationshipWithParent(path: any) { 276 | if (!(path instanceof Path)) { 277 | throw new Error(""); 278 | } 279 | 280 | var pp = path.parentPath; 281 | if (!pp) { 282 | // Orphan paths have no relationship to repair. 283 | return path; 284 | } 285 | 286 | var parentValue = pp.value; 287 | var parentCache = getChildCache(pp); 288 | 289 | // Make sure parentCache[path.name] is populated. 290 | if (parentValue[path.name] === path.value) { 291 | parentCache[path.name] = path; 292 | } else if (isArray.check(parentValue)) { 293 | // Something caused path.name to become out of date, so attempt to 294 | // recover by searching for path.value in parentValue. 295 | var i = parentValue.indexOf(path.value); 296 | if (i >= 0) { 297 | parentCache[path.name = i] = path; 298 | } 299 | } else { 300 | // If path.value disagrees with parentValue[path.name], and 301 | // path.name is not an array index, let path.value become the new 302 | // parentValue[path.name] and update parentCache accordingly. 303 | parentValue[path.name] = path.value; 304 | parentCache[path.name] = path; 305 | } 306 | 307 | if (parentValue[path.name] !== path.value) { 308 | throw new Error(""); 309 | } 310 | if (path.parentPath.get(path.name) !== path) { 311 | throw new Error(""); 312 | } 313 | 314 | return path; 315 | } 316 | 317 | Pp.replace = function replace(replacement) { 318 | var results = []; 319 | var parentValue = this.parentPath.value; 320 | var parentCache = getChildCache(this.parentPath); 321 | var count = arguments.length; 322 | 323 | repairRelationshipWithParent(this); 324 | 325 | if (isArray.check(parentValue)) { 326 | var originalLength = parentValue.length; 327 | var move = getMoves(this.parentPath, count - 1, this.name + 1); 328 | 329 | var spliceArgs: [number, number, ...any[]] = [this.name, 1]; 330 | for (var i = 0; i < count; ++i) { 331 | spliceArgs.push(arguments[i]); 332 | } 333 | 334 | var splicedOut = parentValue.splice.apply(parentValue, spliceArgs); 335 | 336 | if (splicedOut[0] !== this.value) { 337 | throw new Error(""); 338 | } 339 | if (parentValue.length !== (originalLength - 1 + count)) { 340 | throw new Error(""); 341 | } 342 | 343 | move(); 344 | 345 | if (count === 0) { 346 | delete this.value; 347 | delete parentCache[this.name]; 348 | this.__childCache = null; 349 | 350 | } else { 351 | if (parentValue[this.name] !== replacement) { 352 | throw new Error(""); 353 | } 354 | 355 | if (this.value !== replacement) { 356 | this.value = replacement; 357 | this.__childCache = null; 358 | } 359 | 360 | for (i = 0; i < count; ++i) { 361 | results.push(this.parentPath.get(this.name + i)); 362 | } 363 | 364 | if (results[0] !== this) { 365 | throw new Error(""); 366 | } 367 | } 368 | 369 | } else if (count === 1) { 370 | if (this.value !== replacement) { 371 | this.__childCache = null; 372 | } 373 | this.value = parentValue[this.name] = replacement; 374 | results.push(this); 375 | 376 | } else if (count === 0) { 377 | delete parentValue[this.name]; 378 | delete this.value; 379 | this.__childCache = null; 380 | 381 | // Leave this path cached as parentCache[this.name], even though 382 | // it no longer has a value defined. 383 | 384 | } else { 385 | throw new Error("Could not replace path"); 386 | } 387 | 388 | return results; 389 | }; 390 | 391 | return Path; 392 | }; 393 | 394 | maybeSetModuleExports(() => module); 395 | -------------------------------------------------------------------------------- /src/scope.ts: -------------------------------------------------------------------------------- 1 | import { NodePath } from "./node-path"; 2 | import { maybeSetModuleExports } from "./shared"; 3 | import typesPlugin, { Fork } from "./types"; 4 | 5 | var hasOwn = Object.prototype.hasOwnProperty; 6 | 7 | export interface Scope { 8 | path: NodePath; 9 | node: any; 10 | isGlobal: boolean; 11 | depth: number; 12 | parent: any; 13 | bindings: any; 14 | types: any; 15 | didScan: boolean; 16 | declares(name: any): any 17 | declaresType(name: any): any 18 | declareTemporary(prefix?: any): any; 19 | injectTemporary(identifier: any, init: any): any; 20 | scan(force?: any): any; 21 | getBindings(): any; 22 | getTypes(): any; 23 | lookup(name: any): any; 24 | lookupType(name: any): any; 25 | getGlobalScope(): Scope; 26 | } 27 | 28 | export interface ScopeConstructor { 29 | new(path: NodePath, parentScope: any): Scope; 30 | isEstablishedBy(node: any): any; 31 | } 32 | 33 | export default function scopePlugin(fork: Fork) { 34 | var types = fork.use(typesPlugin); 35 | var Type = types.Type; 36 | var namedTypes = types.namedTypes; 37 | var Node = namedTypes.Node; 38 | var Expression = namedTypes.Expression; 39 | var isArray = types.builtInTypes.array; 40 | var b = types.builders; 41 | 42 | const Scope = function Scope(this: Scope, path: NodePath, parentScope: unknown) { 43 | if (!(this instanceof Scope)) { 44 | throw new Error("Scope constructor cannot be invoked without 'new'"); 45 | } 46 | if (!TypeParameterScopeType.check(path.value)) { 47 | ScopeType.assert(path.value); 48 | } 49 | 50 | var depth: number; 51 | 52 | if (parentScope) { 53 | if (!(parentScope instanceof Scope)) { 54 | throw new Error(""); 55 | } 56 | depth = (parentScope as Scope).depth + 1; 57 | } else { 58 | parentScope = null; 59 | depth = 0; 60 | } 61 | 62 | Object.defineProperties(this, { 63 | path: { value: path }, 64 | node: { value: path.value }, 65 | isGlobal: { value: !parentScope, enumerable: true }, 66 | depth: { value: depth }, 67 | parent: { value: parentScope }, 68 | bindings: { value: {} }, 69 | types: { value: {} }, 70 | }); 71 | } as any as ScopeConstructor; 72 | 73 | var ScopeType = Type.or( 74 | // Program nodes introduce global scopes. 75 | namedTypes.Program, 76 | 77 | // Function is the supertype of FunctionExpression, 78 | // FunctionDeclaration, ArrowExpression, etc. 79 | namedTypes.Function, 80 | 81 | // In case you didn't know, the caught parameter shadows any variable 82 | // of the same name in an outer scope. 83 | namedTypes.CatchClause 84 | ); 85 | 86 | // These types introduce scopes that are restricted to type parameters in 87 | // Flow (this doesn't apply to ECMAScript). 88 | var TypeParameterScopeType = Type.or( 89 | namedTypes.Function, 90 | namedTypes.ClassDeclaration, 91 | namedTypes.ClassExpression, 92 | namedTypes.InterfaceDeclaration, 93 | namedTypes.TSInterfaceDeclaration, 94 | namedTypes.TypeAlias, 95 | namedTypes.TSTypeAliasDeclaration, 96 | ); 97 | 98 | var FlowOrTSTypeParameterType = Type.or( 99 | namedTypes.TypeParameter, 100 | namedTypes.TSTypeParameter, 101 | ); 102 | 103 | Scope.isEstablishedBy = function(node) { 104 | return ScopeType.check(node) || TypeParameterScopeType.check(node); 105 | }; 106 | 107 | var Sp: Scope = Scope.prototype; 108 | 109 | // Will be overridden after an instance lazily calls scanScope. 110 | Sp.didScan = false; 111 | 112 | Sp.declares = function(name) { 113 | this.scan(); 114 | return hasOwn.call(this.bindings, name); 115 | }; 116 | 117 | Sp.declaresType = function(name) { 118 | this.scan(); 119 | return hasOwn.call(this.types, name); 120 | }; 121 | 122 | Sp.declareTemporary = function(prefix) { 123 | if (prefix) { 124 | if (!/^[a-z$_]/i.test(prefix)) { 125 | throw new Error(""); 126 | } 127 | } else { 128 | prefix = "t$"; 129 | } 130 | 131 | // Include this.depth in the name to make sure the name does not 132 | // collide with any variables in nested/enclosing scopes. 133 | prefix += this.depth.toString(36) + "$"; 134 | 135 | this.scan(); 136 | 137 | var index = 0; 138 | while (this.declares(prefix + index)) { 139 | ++index; 140 | } 141 | 142 | var name = prefix + index; 143 | return this.bindings[name] = types.builders.identifier(name); 144 | }; 145 | 146 | Sp.injectTemporary = function(identifier, init) { 147 | identifier || (identifier = this.declareTemporary()); 148 | 149 | var bodyPath = this.path.get("body"); 150 | if (namedTypes.BlockStatement.check(bodyPath.value)) { 151 | bodyPath = bodyPath.get("body"); 152 | } 153 | 154 | bodyPath.unshift( 155 | b.variableDeclaration( 156 | "var", 157 | [b.variableDeclarator(identifier, init || null)] 158 | ) 159 | ); 160 | 161 | return identifier; 162 | }; 163 | 164 | Sp.scan = function(force) { 165 | if (force || !this.didScan) { 166 | for (var name in this.bindings) { 167 | // Empty out this.bindings, just in cases. 168 | delete this.bindings[name]; 169 | } 170 | for (var name in this.types) { 171 | // Empty out this.types, just in cases. 172 | delete this.types[name]; 173 | } 174 | scanScope(this.path, this.bindings, this.types); 175 | this.didScan = true; 176 | } 177 | }; 178 | 179 | Sp.getBindings = function () { 180 | this.scan(); 181 | return this.bindings; 182 | }; 183 | 184 | Sp.getTypes = function () { 185 | this.scan(); 186 | return this.types; 187 | }; 188 | 189 | function scanScope(path: NodePath, bindings: any, scopeTypes: any) { 190 | var node = path.value; 191 | if (TypeParameterScopeType.check(node)) { 192 | const params = path.get('typeParameters', 'params'); 193 | if (isArray.check(params.value)) { 194 | params.each((childPath: NodePath) => { 195 | addTypeParameter(childPath, scopeTypes); 196 | }); 197 | } 198 | } 199 | if (ScopeType.check(node)) { 200 | if (namedTypes.CatchClause.check(node)) { 201 | // A catch clause establishes a new scope but the only variable 202 | // bound in that scope is the catch parameter. Any other 203 | // declarations create bindings in the outer scope. 204 | addPattern(path.get("param"), bindings); 205 | } else { 206 | recursiveScanScope(path, bindings, scopeTypes); 207 | } 208 | } 209 | } 210 | 211 | function recursiveScanScope(path: NodePath, bindings: any, scopeTypes: any) { 212 | var node = path.value; 213 | 214 | if (path.parent && 215 | namedTypes.FunctionExpression.check(path.parent.node) && 216 | path.parent.node.id) { 217 | addPattern(path.parent.get("id"), bindings); 218 | } 219 | 220 | if (!node) { 221 | // None of the remaining cases matter if node is falsy. 222 | 223 | } else if (isArray.check(node)) { 224 | path.each((childPath: NodePath) => { 225 | recursiveScanChild(childPath, bindings, scopeTypes); 226 | }); 227 | 228 | } else if (namedTypes.Function.check(node)) { 229 | path.get("params").each((paramPath: NodePath) => { 230 | addPattern(paramPath, bindings); 231 | }); 232 | 233 | recursiveScanChild(path.get("body"), bindings, scopeTypes); 234 | recursiveScanScope(path.get("typeParameters"), bindings, scopeTypes); 235 | 236 | } else if ( 237 | (namedTypes.TypeAlias && namedTypes.TypeAlias.check(node)) || 238 | (namedTypes.InterfaceDeclaration && namedTypes.InterfaceDeclaration.check(node)) || 239 | (namedTypes.TSTypeAliasDeclaration && namedTypes.TSTypeAliasDeclaration.check(node)) || 240 | (namedTypes.TSInterfaceDeclaration && namedTypes.TSInterfaceDeclaration.check(node)) 241 | ) { 242 | addTypePattern(path.get("id"), scopeTypes); 243 | 244 | } else if (namedTypes.VariableDeclarator.check(node)) { 245 | addPattern(path.get("id"), bindings); 246 | recursiveScanChild(path.get("init"), bindings, scopeTypes); 247 | 248 | } else if (node.type === "ImportSpecifier" || 249 | node.type === "ImportNamespaceSpecifier" || 250 | node.type === "ImportDefaultSpecifier") { 251 | addPattern( 252 | // Esprima used to use the .name field to refer to the local 253 | // binding identifier for ImportSpecifier nodes, but .id for 254 | // ImportNamespaceSpecifier and ImportDefaultSpecifier nodes. 255 | // ESTree/Acorn/ESpree use .local for all three node types. 256 | path.get(node.local ? "local" : 257 | node.name ? "name" : "id"), 258 | bindings 259 | ); 260 | 261 | } else if (Node.check(node) && !Expression.check(node)) { 262 | types.eachField(node, function(name: any, child: any) { 263 | var childPath = path.get(name); 264 | if (!pathHasValue(childPath, child)) { 265 | throw new Error(""); 266 | } 267 | recursiveScanChild(childPath, bindings, scopeTypes); 268 | }); 269 | } 270 | } 271 | 272 | function pathHasValue(path: NodePath, value: any) { 273 | if (path.value === value) { 274 | return true; 275 | } 276 | 277 | // Empty arrays are probably produced by defaults.emptyArray, in which 278 | // case is makes sense to regard them as equivalent, if not ===. 279 | if (Array.isArray(path.value) && 280 | path.value.length === 0 && 281 | Array.isArray(value) && 282 | value.length === 0) { 283 | return true; 284 | } 285 | 286 | return false; 287 | } 288 | 289 | function recursiveScanChild(path: NodePath, bindings: any, scopeTypes: any) { 290 | var node = path.value; 291 | 292 | if (!node || Expression.check(node)) { 293 | // Ignore falsy values and Expressions. 294 | 295 | } else if (namedTypes.FunctionDeclaration.check(node) && 296 | node.id !== null) { 297 | addPattern(path.get("id"), bindings); 298 | 299 | } else if (namedTypes.ClassDeclaration && 300 | namedTypes.ClassDeclaration.check(node) && 301 | node.id !== null) { 302 | addPattern(path.get("id"), bindings); 303 | recursiveScanScope(path.get("typeParameters"), bindings, scopeTypes); 304 | 305 | } else if ( 306 | (namedTypes.InterfaceDeclaration && 307 | namedTypes.InterfaceDeclaration.check(node)) || 308 | (namedTypes.TSInterfaceDeclaration && 309 | namedTypes.TSInterfaceDeclaration.check(node)) 310 | ) { 311 | addTypePattern(path.get("id"), scopeTypes); 312 | 313 | } else if (ScopeType.check(node)) { 314 | if ( 315 | namedTypes.CatchClause.check(node) && 316 | // TODO Broaden this to accept any pattern. 317 | namedTypes.Identifier.check(node.param) 318 | ) { 319 | var catchParamName = node.param.name; 320 | var hadBinding = hasOwn.call(bindings, catchParamName); 321 | 322 | // Any declarations that occur inside the catch body that do 323 | // not have the same name as the catch parameter should count 324 | // as bindings in the outer scope. 325 | recursiveScanScope(path.get("body"), bindings, scopeTypes); 326 | 327 | // If a new binding matching the catch parameter name was 328 | // created while scanning the catch body, ignore it because it 329 | // actually refers to the catch parameter and not the outer 330 | // scope that we're currently scanning. 331 | if (!hadBinding) { 332 | delete bindings[catchParamName]; 333 | } 334 | } 335 | 336 | } else { 337 | recursiveScanScope(path, bindings, scopeTypes); 338 | } 339 | } 340 | 341 | function addPattern(patternPath: NodePath, bindings: any) { 342 | var pattern = patternPath.value; 343 | namedTypes.Pattern.assert(pattern); 344 | 345 | if (namedTypes.Identifier.check(pattern)) { 346 | if (hasOwn.call(bindings, pattern.name)) { 347 | bindings[pattern.name].push(patternPath); 348 | } else { 349 | bindings[pattern.name] = [patternPath]; 350 | } 351 | 352 | } else if (namedTypes.AssignmentPattern && 353 | namedTypes.AssignmentPattern.check(pattern)) { 354 | addPattern(patternPath.get('left'), bindings); 355 | 356 | } else if ( 357 | namedTypes.ObjectPattern && 358 | namedTypes.ObjectPattern.check(pattern) 359 | ) { 360 | patternPath.get('properties').each(function(propertyPath: any) { 361 | var property = propertyPath.value; 362 | if (namedTypes.Pattern.check(property)) { 363 | addPattern(propertyPath, bindings); 364 | } else if ( 365 | namedTypes.Property.check(property) || 366 | (namedTypes.ObjectProperty && 367 | namedTypes.ObjectProperty.check(property)) 368 | ) { 369 | addPattern(propertyPath.get('value'), bindings); 370 | } else if ( 371 | namedTypes.SpreadProperty && 372 | namedTypes.SpreadProperty.check(property) 373 | ) { 374 | addPattern(propertyPath.get('argument'), bindings); 375 | } 376 | }); 377 | 378 | } else if ( 379 | namedTypes.ArrayPattern && 380 | namedTypes.ArrayPattern.check(pattern) 381 | ) { 382 | patternPath.get('elements').each(function(elementPath: any) { 383 | var element = elementPath.value; 384 | if (namedTypes.Pattern.check(element)) { 385 | addPattern(elementPath, bindings); 386 | } else if ( 387 | namedTypes.SpreadElement && 388 | namedTypes.SpreadElement.check(element) 389 | ) { 390 | addPattern(elementPath.get("argument"), bindings); 391 | } 392 | }); 393 | 394 | } else if ( 395 | namedTypes.PropertyPattern && 396 | namedTypes.PropertyPattern.check(pattern) 397 | ) { 398 | addPattern(patternPath.get('pattern'), bindings); 399 | 400 | } else if ( 401 | (namedTypes.SpreadElementPattern && 402 | namedTypes.SpreadElementPattern.check(pattern)) || 403 | (namedTypes.RestElement && 404 | namedTypes.RestElement.check(pattern)) || 405 | (namedTypes.SpreadPropertyPattern && 406 | namedTypes.SpreadPropertyPattern.check(pattern)) 407 | ) { 408 | addPattern(patternPath.get('argument'), bindings); 409 | } 410 | } 411 | 412 | function addTypePattern(patternPath: NodePath, types: any) { 413 | var pattern = patternPath.value; 414 | namedTypes.Pattern.assert(pattern); 415 | 416 | if (namedTypes.Identifier.check(pattern)) { 417 | if (hasOwn.call(types, pattern.name)) { 418 | types[pattern.name].push(patternPath); 419 | } else { 420 | types[pattern.name] = [patternPath]; 421 | } 422 | } 423 | } 424 | 425 | function addTypeParameter(parameterPath: NodePath, types: any) { 426 | var parameter = parameterPath.value; 427 | FlowOrTSTypeParameterType.assert(parameter); 428 | 429 | if (hasOwn.call(types, parameter.name)) { 430 | types[parameter.name].push(parameterPath); 431 | } else { 432 | types[parameter.name] = [parameterPath]; 433 | } 434 | } 435 | 436 | Sp.lookup = function(name) { 437 | for (var scope = this; scope; scope = scope.parent) 438 | if (scope.declares(name)) 439 | break; 440 | return scope; 441 | }; 442 | 443 | Sp.lookupType = function(name) { 444 | for (var scope = this; scope; scope = scope.parent) 445 | if (scope.declaresType(name)) 446 | break; 447 | return scope; 448 | }; 449 | 450 | Sp.getGlobalScope = function() { 451 | var scope = this; 452 | while (!scope.isGlobal) 453 | scope = scope.parent; 454 | return scope; 455 | }; 456 | 457 | return Scope; 458 | }; 459 | 460 | maybeSetModuleExports(() => module); 461 | -------------------------------------------------------------------------------- /src/shared.ts: -------------------------------------------------------------------------------- 1 | import typesPlugin, { Fork } from "./types"; 2 | 3 | export default function (fork: Fork) { 4 | var types = fork.use(typesPlugin); 5 | var Type = types.Type; 6 | var builtin = types.builtInTypes; 7 | var isNumber = builtin.number; 8 | 9 | // An example of constructing a new type with arbitrary constraints from 10 | // an existing type. 11 | function geq(than: any) { 12 | return Type.from( 13 | (value: number) => isNumber.check(value) && value >= than, 14 | isNumber + " >= " + than, 15 | ); 16 | }; 17 | 18 | // Default value-returning functions that may optionally be passed as a 19 | // third argument to Def.prototype.field. 20 | const defaults = { 21 | // Functions were used because (among other reasons) that's the most 22 | // elegant way to allow for the emptyArray one always to give a new 23 | // array instance. 24 | "null": function () { return null }, 25 | "emptyArray": function () { return [] }, 26 | "false": function () { return false }, 27 | "true": function () { return true }, 28 | "undefined": function () {}, 29 | "use strict": function () { return "use strict"; } 30 | }; 31 | 32 | var naiveIsPrimitive = Type.or( 33 | builtin.string, 34 | builtin.number, 35 | builtin.boolean, 36 | builtin.null, 37 | builtin.undefined 38 | ); 39 | 40 | const isPrimitive = Type.from( 41 | (value: any) => { 42 | if (value === null) 43 | return true; 44 | var type = typeof value; 45 | if (type === "object" || 46 | type === "function") { 47 | return false; 48 | } 49 | return true; 50 | }, 51 | naiveIsPrimitive.toString(), 52 | ); 53 | 54 | return { 55 | geq, 56 | defaults, 57 | isPrimitive, 58 | }; 59 | }; 60 | 61 | // I would use `typeof module` for this, but that would add 62 | // 63 | // /// 64 | // 65 | // at the top of shared.d.ts, which pulls in @types/node, which we should try to 66 | // avoid unless absolutely necessary, due to the risk of conflict with other 67 | // copies of @types/node. 68 | interface NodeModule { 69 | exports: { 70 | default?: any; 71 | __esModule?: boolean; 72 | }; 73 | } 74 | 75 | // This function accepts a getter function that should return an object 76 | // conforming to the NodeModule interface above. Typically, this means calling 77 | // maybeSetModuleExports(() => module) at the very end of any module that has a 78 | // default export, so the default export value can replace module.exports and 79 | // thus CommonJS consumers can continue to rely on require("./that/module") 80 | // returning the default-exported value, rather than always returning an exports 81 | // object with a default property equal to that value. This function should help 82 | // preserve backwards compatibility for CommonJS consumers, as a replacement for 83 | // the ts-add-module-exports package. 84 | export function maybeSetModuleExports( 85 | moduleGetter: () => NodeModule, 86 | ) { 87 | try { 88 | var nodeModule = moduleGetter(); 89 | var originalExports = nodeModule.exports; 90 | var defaultExport = originalExports["default"]; 91 | } catch { 92 | // It's normal/acceptable for this code to throw a ReferenceError due to 93 | // the moduleGetter function attempting to access a non-existent global 94 | // `module` variable. That's the reason we use a getter function here: 95 | // so the calling code doesn't have to do its own typeof module === 96 | // "object" checking (because it's always safe to pass `() => module` as 97 | // an argument, even when `module` is not defined in the calling scope). 98 | return; 99 | } 100 | 101 | if (defaultExport && 102 | defaultExport !== originalExports && 103 | typeof originalExports === "object" 104 | ) { 105 | // Make all properties found in originalExports properties of the 106 | // default export, including the default property itself, so that 107 | // require(nodeModule.id).default === require(nodeModule.id). 108 | Object.assign(defaultExport, originalExports, { "default": defaultExport }); 109 | // Object.assign only transfers enumerable properties, and 110 | // __esModule is (and should remain) non-enumerable. 111 | if (originalExports.__esModule) { 112 | Object.defineProperty(defaultExport, "__esModule", { value: true }); 113 | } 114 | // This line allows require(nodeModule.id) === defaultExport, rather 115 | // than (only) require(nodeModule.id).default === defaultExport. 116 | nodeModule.exports = defaultExport; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/api.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { namedTypes, builders } from "../main"; 3 | import * as types from "../main"; 4 | 5 | describe("namedTypes", function () { 6 | it("should work as a namespace", function () { 7 | const id = builders.identifier("oyez"); 8 | namedTypes.Identifier.assert(id); 9 | }); 10 | 11 | it("should work as a type", function () { 12 | function getIdName(id: namedTypes.Identifier) { 13 | return id.name; 14 | } 15 | assert.strictEqual( 16 | getIdName(builders.identifier("oyez")), 17 | "oyez", 18 | ); 19 | }); 20 | 21 | it("should work as a value", function () { 22 | assert.strictEqual(typeof namedTypes, "object"); 23 | assert.strictEqual(typeof namedTypes.IfStatement, "object"); 24 | }); 25 | }); 26 | 27 | describe("types.namedTypes", function () { 28 | it("should work as a namespace", function () { 29 | const id = types.builders.identifier("oyez"); 30 | types.namedTypes.Identifier.assert(id); 31 | }); 32 | 33 | it("should work as a type", function () { 34 | function getIdName(id: types.namedTypes.Identifier) { 35 | return id.name; 36 | } 37 | assert.strictEqual( 38 | getIdName(types.builders.identifier("oyez")), 39 | "oyez", 40 | ); 41 | }); 42 | 43 | it("should work as a value", function () { 44 | assert.strictEqual(typeof types.namedTypes, "object"); 45 | assert.strictEqual(typeof types.namedTypes.IfStatement, "object"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/test/flow.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import flowParser from "flow-parser"; 3 | import forkFn from "../fork"; 4 | import flowDef from "../def/flow"; 5 | import { ASTNode } from "../types"; 6 | import { NodePath } from "../node-path"; 7 | import { Visitor } from "../gen/visitor"; 8 | import { Context } from "../path-visitor"; 9 | 10 | var types = forkFn([ 11 | flowDef, 12 | ]); 13 | 14 | describe("flow types", function () { 15 | it("issue #242", function () { 16 | const parser = { 17 | parse(code: string) { 18 | return flowParser.parse(code, { 19 | types: true 20 | }); 21 | } 22 | }; 23 | 24 | const program = parser.parse([ 25 | "class A extends C {}", 26 | "function f() {}", 27 | ].join("\n")); 28 | 29 | const identifierNames: any[] = []; 30 | const typeParamNames: any[] = [] 31 | 32 | types.visit(program, { 33 | visitIdentifier(path: any) { 34 | identifierNames.push(path.node.name); 35 | this.traverse(path); 36 | }, 37 | 38 | visitTypeParameter(path: any) { 39 | typeParamNames.push(path.node.name); 40 | this.traverse(path); 41 | } 42 | }); 43 | 44 | assert.deepEqual(identifierNames, ["A", "C", "D", "f"]); 45 | assert.deepEqual(typeParamNames, ["B", "E"]); 46 | }); 47 | 48 | it("issue #261", function () { 49 | const parser = { 50 | parse(code: string) { 51 | return flowParser.parse(code, { 52 | types: true 53 | }); 54 | } 55 | }; 56 | 57 | const program = parser.parse('declare module.exports: {};'); 58 | 59 | assert.strictEqual(program.body[0].type, 'DeclareModuleExports'); 60 | assert.notEqual(program.body[0].typeAnnotation, undefined); 61 | assert.strictEqual(program.body[0].typeAnnotation.type, 'TypeAnnotation'); 62 | }); 63 | 64 | it("Explicit type arguments", function () { 65 | const parser = { 66 | parse(code: string) { 67 | return flowParser.parse(code, { 68 | types: true 69 | }); 70 | } 71 | }; 72 | 73 | const program = parser.parse([ 74 | 'test();', 75 | 'test();', 76 | 'new test();', 77 | 'new test();', 78 | ].join("\n")); 79 | 80 | const typeParamNames: any[] = [] 81 | 82 | types.visit(program, { 83 | visitGenericTypeAnnotation(path: any) { 84 | typeParamNames.push(path.node.id.name); 85 | this.traverse(path); 86 | } 87 | }); 88 | 89 | assert.deepEqual(typeParamNames, ["A", "B", "C", "D", "E", "F"]); 90 | }); 91 | 92 | describe('scope', () => { 93 | const scope = [ 94 | "type Foo = {}", 95 | "interface Bar {}" 96 | ]; 97 | 98 | const ast = flowParser.parse(scope.join("\n")); 99 | 100 | it("should register flow types with the scope", function() { 101 | types.visit(ast, { 102 | visitProgram(path: any) { 103 | assert(path.scope.declaresType('Foo')); 104 | assert(path.scope.declaresType('Bar')); 105 | assert.equal(path.scope.lookupType('Foo').getTypes()['Foo'][0].parent.node.type, 'TypeAlias'); 106 | assert.equal(path.scope.lookupType('Bar').getTypes()['Bar'][0].parent.node.type, 'InterfaceDeclaration'); 107 | return false; 108 | } 109 | }); 110 | }); 111 | }); 112 | 113 | function assertVisited(node: ASTNode, visitors: Visitor): any { 114 | const visitedSet: Set = new Set(); 115 | const wrappedVisitors: Visitor = {} 116 | for (const _key of Object.keys(visitors)) { 117 | const key = _key as keyof Visitor 118 | wrappedVisitors[key] = function (this: Context, path: NodePath) { 119 | visitedSet.add(key); 120 | (visitors[key] as any)?.call(this, path) 121 | } 122 | } 123 | types.visit(node, wrappedVisitors); 124 | 125 | for (const key of Object.keys(visitors)) { 126 | assert.equal(visitedSet.has(key), true); 127 | } 128 | } 129 | 130 | it("issue #294 - function declarations", function () { 131 | const parser = { 132 | parse(code: string) { 133 | return require('flow-parser').parse(code, { 134 | types: true 135 | }); 136 | } 137 | }; 138 | 139 | const program = parser.parse([ 140 | "function foo(): T { }", 141 | "let bar: T", 142 | ].join("\n")); 143 | 144 | assertVisited(program, { 145 | visitFunctionDeclaration(path) { 146 | assert.ok(path.scope.lookupType('T')); 147 | this.traverse(path); 148 | }, 149 | visitVariableDeclarator(path) { 150 | assert.equal(path.scope.lookupType('T'), null); 151 | this.traverse(path); 152 | } 153 | }); 154 | }); 155 | 156 | it("issue #294 - function expressions", function () { 157 | const parser = { 158 | parse(code: string) { 159 | return require('flow-parser').parse(code, { 160 | types: true 161 | }); 162 | } 163 | }; 164 | 165 | const program = parser.parse([ 166 | "const foo = function (): T { }", 167 | "let bar: T", 168 | ].join("\n")); 169 | 170 | assertVisited(program, { 171 | visitFunctionExpression(path) { 172 | assert.ok(path.scope.lookupType('T')); 173 | this.traverse(path); 174 | }, 175 | visitVariableDeclarator(path) { 176 | if (path.node.id.type === 'Identifier' && path.node.id.name === 'bar') { 177 | assert.equal(path.scope.lookupType('T'), null); 178 | } 179 | this.traverse(path); 180 | } 181 | }); 182 | }); 183 | 184 | it("issue #294 - arrow function expressions", function () { 185 | const parser = { 186 | parse(code: string) { 187 | return require('flow-parser').parse(code, { 188 | types: true 189 | }); 190 | } 191 | }; 192 | 193 | const program = parser.parse([ 194 | "const foo = (): T => { }", 195 | "let bar: T" 196 | ].join("\n")); 197 | 198 | assertVisited(program, { 199 | visitArrowFunctionExpression(path) { 200 | assert.ok(path.scope.lookupType('T')); 201 | this.traverse(path); 202 | }, 203 | visitVariableDeclarator(path) { 204 | assert.equal(path.scope.lookupType('T'), null); 205 | this.traverse(path); 206 | } 207 | }); 208 | }); 209 | 210 | it("issue #294 - class declarations", function () { 211 | const parser = { 212 | parse(code: string) { 213 | return require('flow-parser').parse(code, { 214 | types: true 215 | }); 216 | } 217 | }; 218 | 219 | const program = parser.parse([ 220 | "class Foo extends Bar> { }", 221 | "let bar: T" 222 | ].join("\n")); 223 | 224 | assertVisited(program, { 225 | visitTypeParameterInstantiation(path) { 226 | assert.ok(path.scope.lookupType('T')); 227 | this.traverse(path); 228 | }, 229 | visitVariableDeclarator(path) { 230 | assert.equal(path.scope.lookupType('T'), null); 231 | this.traverse(path); 232 | } 233 | }); 234 | }); 235 | 236 | it("issue #294 - class expressions", function () { 237 | const parser = { 238 | parse(code: string) { 239 | return require('flow-parser').parse(code, { 240 | types: true 241 | }); 242 | } 243 | }; 244 | 245 | const program = parser.parse([ 246 | "const foo = class Foo extends Bar> { }", 247 | "let bar: T" 248 | ].join("\n")); 249 | 250 | assertVisited(program, { 251 | visitTypeParameterInstantiation(path) { 252 | assert.ok(path.scope.lookupType('T')); 253 | this.traverse(path); 254 | }, 255 | visitVariableDeclarator(path) { 256 | if (path.node.id.type === 'Identifier' && path.node.id.name === 'bar') { 257 | assert.equal(path.scope.lookupType('T'), null); 258 | assert.equal(path.scope.lookupType('Foo'), null); 259 | } 260 | this.traverse(path); 261 | } 262 | }); 263 | }); 264 | 265 | it("issue #296 - interface declarations", function () { 266 | const parser = { 267 | parse(code: string) { 268 | return require('flow-parser').parse(code, { 269 | types: true 270 | }); 271 | } 272 | }; 273 | 274 | const program = parser.parse([ 275 | "interface Foo extends Bar> { }", 276 | "let bar: T" 277 | ].join("\n")); 278 | 279 | assertVisited(program, { 280 | visitTypeParameterInstantiation(path) { 281 | assert.ok(path.scope.lookupType('T')); 282 | this.traverse(path); 283 | }, 284 | visitVariableDeclarator(path) { 285 | assert.equal(path.scope.lookupType('T'), null); 286 | assert.ok(path.scope.lookupType('Foo')); 287 | this.traverse(path); 288 | } 289 | }); 290 | }); 291 | }); 292 | -------------------------------------------------------------------------------- /src/test/perf.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs"; 3 | import { parse } from "esprima"; 4 | // @ts-ignore Cannot find module 'ast-types'. [2307] 5 | import { visit } from "ast-types"; 6 | 7 | var backbone = fs.readFileSync( 8 | path.join(__dirname, "data", "backbone.js"), 9 | "utf-8" 10 | ); 11 | 12 | var ast = parse(backbone); 13 | 14 | var names: any[] = []; 15 | var start = +new Date; 16 | 17 | visit(ast, { 18 | visitNode: function(path: any) { 19 | names.push(path.name); 20 | this.traverse(path); 21 | } 22 | }); 23 | 24 | console.log(names.length); 25 | console.log(+new Date - start, "ms"); 26 | -------------------------------------------------------------------------------- /src/test/run.ts: -------------------------------------------------------------------------------- 1 | import "./api"; 2 | import "./ecmascript"; 3 | import "./typescript"; 4 | import "./flow"; 5 | import "./type-annotations"; 6 | -------------------------------------------------------------------------------- /src/test/shared.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs"; 3 | import { parse as esprimaParse } from "esprima"; 4 | import { parse as reifyBabylonParse } from "reify/lib/parsers/babylon"; 5 | import { namedTypes as n } from "../main"; 6 | 7 | export function validateECMAScript(file: any) { 8 | var fullPath = path.join(__dirname, "..", "..", "src", file); 9 | 10 | it("should validate " + file + " with Esprima", function (done) { 11 | fs.readFile(fullPath, "utf8", function(err, code) { 12 | if (err) { 13 | throw err; 14 | } 15 | 16 | n.Program.assert(esprimaParse(code), true); 17 | n.Program.assert(esprimaParse(code, { 18 | loc: true 19 | }), true); 20 | 21 | done(); 22 | }); 23 | }); 24 | 25 | it("should validate " + file + " with Babylon", function (done) { 26 | fs.readFile(fullPath, "utf8", function (err, code) { 27 | if (err) { 28 | throw err; 29 | } 30 | var ast = babylonParse(code); 31 | n.Program.assert(ast, true); 32 | done(); 33 | }); 34 | }); 35 | }; 36 | 37 | export function babylonParse(source: any, options?: any) { 38 | var ast = reifyBabylonParse(source, options); 39 | if (ast.type === "File") ast = ast.program; 40 | return ast; 41 | } 42 | 43 | export { esprimaParse }; 44 | 45 | // Helper for determining if we should care that a given type is not defined yet. 46 | // TODO Periodically revisit this as proposals advance. 47 | export function isEarlyStageProposalType(typeName: string) { 48 | switch (typeName) { 49 | // The pipeline operator syntax is still at Stage 1: 50 | // https://github.com/tc39/proposals#stage-1 51 | case "PipelineTopicExpression": 52 | case "PipelineBareFunction": 53 | case "PipelinePrimaryTopicReference": 54 | // A Babel-specific AST innovation: 55 | // https://github.com/babel/babel/pull/9364 56 | case "Placeholder": 57 | // Partial application proposal (stage 1): 58 | // https://github.com/babel/babel/pull/9474 59 | case "ArgumentPlaceholder": 60 | return true; 61 | default: 62 | return false; 63 | } 64 | } 65 | 66 | export function hasNonEmptyErrorsArray( 67 | value: any 68 | ): value is { errors: [any, ...any[]] } { 69 | return !!( 70 | value && 71 | typeof value === "object" && 72 | Array.isArray(value.errors) && 73 | value.errors.length > 0 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/test/type-annotations.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import * as types from "../main"; 3 | 4 | describe("type annotations", function () { 5 | it("can build Identifier with Flow typeAnnotation", function () { 6 | assert.doesNotThrow(function () { 7 | types.builders.identifier.from({ 8 | name: "x", 9 | typeAnnotation: types.builders.typeAnnotation(types.builders.stringTypeAnnotation()) 10 | }); 11 | }) 12 | }); 13 | 14 | it("can build Identifier with TS typeAnnotation", function () { 15 | assert.doesNotThrow(function () { 16 | types.builders.identifier.from({ 17 | name: "x", 18 | typeAnnotation: types.builders.tsTypeAnnotation(types.builders.tsStringKeyword()) 19 | }); 20 | }); 21 | }); 22 | 23 | it("can build ObjectPattern with Flow typeAnnotation", function () { 24 | assert.doesNotThrow(function () { 25 | types.builders.objectPattern.from({ 26 | properties: [ 27 | types.builders.objectProperty( 28 | types.builders.identifier("x"), 29 | types.builders.identifier("y") 30 | ), 31 | ], 32 | typeAnnotation: types.builders.typeAnnotation( 33 | types.builders.genericTypeAnnotation(types.builders.identifier("SomeType"), null) 34 | ), 35 | }); 36 | }); 37 | }); 38 | 39 | it("can build ObjectPattern with TS typeAnnotation", function () { 40 | assert.doesNotThrow(function () { 41 | types.builders.objectPattern.from({ 42 | properties: [ 43 | types.builders.objectProperty( 44 | types.builders.identifier("x"), 45 | types.builders.identifier("y") 46 | ), 47 | ], 48 | typeAnnotation: types.builders.tsTypeAnnotation( 49 | types.builders.tsTypeReference(types.builders.identifier("SomeType")) 50 | ) 51 | }); 52 | }); 53 | }); 54 | 55 | it("can build FunctionDeclaration with Flow typeParameters and returnType", function () { 56 | assert.doesNotThrow(function () { 57 | types.builders.functionDeclaration.from({ 58 | id: types.builders.identifier("someFunction"), 59 | params: [], 60 | typeParameters: types.builders.typeParameterDeclaration([ 61 | types.builders.typeParameter("T") 62 | ]), 63 | returnType: types.builders.typeAnnotation( 64 | types.builders.genericTypeAnnotation(types.builders.identifier("SomeType"), null) 65 | ), 66 | body: types.builders.blockStatement([]) 67 | }); 68 | }); 69 | }); 70 | 71 | it("can build FunctionDeclaration with TS typeParameters and returnType", function () { 72 | assert.doesNotThrow(function () { 73 | types.builders.functionDeclaration.from({ 74 | id: types.builders.identifier("someFunction"), 75 | params: [], 76 | typeParameters: types.builders.tsTypeParameterDeclaration([ 77 | types.builders.tsTypeParameter("T") 78 | ]), 79 | returnType: types.builders.tsTypeAnnotation( 80 | types.builders.tsTypeReference(types.builders.identifier("SomeType")) 81 | ), 82 | body: types.builders.blockStatement([]) 83 | }); 84 | }); 85 | }); 86 | 87 | it("can build ClassProperty with Flow typeAnnotation", function () { 88 | assert.doesNotThrow(function () { 89 | types.builders.classProperty.from({ 90 | key: types.builders.identifier("someClassProperty"), 91 | typeAnnotation: types.builders.typeAnnotation(types.builders.stringTypeAnnotation()), 92 | value: null 93 | }); 94 | }); 95 | }); 96 | 97 | it("can build ClassProperty with TS typeAnnotation", function () { 98 | assert.doesNotThrow(function () { 99 | types.builders.classProperty.from({ 100 | key: types.builders.identifier("someClassProperty"), 101 | typeAnnotation: types.builders.tsTypeAnnotation(types.builders.tsStringKeyword()), 102 | value: null 103 | }); 104 | }); 105 | }); 106 | 107 | it("can build ClassDeclaration with Flow typeParameters and superTypeParameters", function () { 108 | assert.doesNotThrow(function () { 109 | types.builders.classDeclaration.from({ 110 | id: types.builders.identifier("SomeClass"), 111 | typeParameters: types.builders.typeParameterDeclaration([ 112 | types.builders.typeParameter("T") 113 | ]), 114 | superClass: types.builders.identifier("SomeSuperClass"), 115 | superTypeParameters: types.builders.typeParameterInstantiation([ 116 | types.builders.genericTypeAnnotation(types.builders.identifier("U"), null) 117 | ]), 118 | body: types.builders.classBody([]) 119 | }); 120 | }); 121 | }); 122 | 123 | it("can build ClassDeclaration with TS typeParameters and superTypeParameters", function () { 124 | assert.doesNotThrow(function () { 125 | types.builders.classDeclaration.from({ 126 | id: types.builders.identifier("SomeClass"), 127 | typeParameters: types.builders.tsTypeParameterDeclaration([ 128 | types.builders.tsTypeParameter("T") 129 | ]), 130 | superClass: types.builders.identifier("SomeSuperClass"), 131 | superTypeParameters: types.builders.tsTypeParameterInstantiation([ 132 | types.builders.tsTypeReference(types.builders.identifier("U")) 133 | ]), 134 | body: types.builders.classBody([]) 135 | }); 136 | }); 137 | }); 138 | 139 | it("can build ClassExpression with Flow typeParameters and superTypeParameters", function () { 140 | assert.doesNotThrow(function () { 141 | types.builders.classExpression.from({ 142 | id: types.builders.identifier("SomeClass"), 143 | typeParameters: types.builders.typeParameterDeclaration([ 144 | types.builders.typeParameter("T") 145 | ]), 146 | superClass: types.builders.identifier("SomeSuperClass"), 147 | superTypeParameters: types.builders.typeParameterInstantiation([ 148 | types.builders.genericTypeAnnotation(types.builders.identifier("U"), null) 149 | ]), 150 | body: types.builders.classBody([]) 151 | }); 152 | }); 153 | }); 154 | 155 | it("can build ClassExpression with TS typeParameters and superTypeParameters", function () { 156 | assert.doesNotThrow(function () { 157 | types.builders.classExpression.from({ 158 | id: types.builders.identifier("SomeClass"), 159 | typeParameters: types.builders.tsTypeParameterDeclaration([ 160 | types.builders.tsTypeParameter("T") 161 | ]), 162 | superClass: types.builders.identifier("SomeSuperClass"), 163 | superTypeParameters: types.builders.tsTypeParameterInstantiation([ 164 | types.builders.tsTypeReference(types.builders.identifier("U")) 165 | ]), 166 | body: types.builders.classBody([]) 167 | }); 168 | }); 169 | }); 170 | 171 | it("can build ClassDeclaration with Flow implements", function () { 172 | assert.doesNotThrow(function () { 173 | types.builders.classDeclaration.from({ 174 | id: types.builders.identifier("SomeClass"), 175 | implements: [ 176 | types.builders.classImplements.from({ 177 | id: types.builders.identifier("SomeInterface"), 178 | typeParameters: types.builders.typeParameterInstantiation([ 179 | types.builders.genericTypeAnnotation(types.builders.identifier("U"), null) 180 | ]), 181 | }), 182 | types.builders.classImplements(types.builders.identifier("SomeOtherInterface")) 183 | ], 184 | body: types.builders.classBody([]) 185 | }); 186 | }); 187 | }); 188 | 189 | it("can build ClassDeclaration with TS implements", function () { 190 | assert.doesNotThrow(function () { 191 | types.builders.classDeclaration.from({ 192 | id: types.builders.identifier("SomeClass"), 193 | implements: [ 194 | types.builders.tsExpressionWithTypeArguments.from({ 195 | expression: types.builders.identifier("SomeInterface"), 196 | typeParameters: types.builders.tsTypeParameterInstantiation([ 197 | types.builders.tsTypeReference(types.builders.identifier("U")) 198 | ]), 199 | }), 200 | types.builders.tsExpressionWithTypeArguments( 201 | types.builders.identifier("SomeOtherInterface") 202 | ) 203 | ], 204 | body: types.builders.classBody([]) 205 | }); 206 | }); 207 | }); 208 | }); 209 | -------------------------------------------------------------------------------- /src/test/typescript.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | import glob from "glob"; 5 | import { parse as babelParse, ParseError, ParserOptions, ParserPlugin } from "@babel/parser"; 6 | import fork from "../fork"; 7 | import esProposalsDef from '../def/es-proposals'; 8 | import typescriptDef from "../def/typescript"; 9 | import jsxDef from "../def/jsx"; 10 | import { visit } from "../main"; 11 | import { ASTNode } from "../types"; 12 | import { NodePath } from "../node-path"; 13 | import { Visitor } from "../gen/visitor"; 14 | import { Context } from "../path-visitor"; 15 | import { hasNonEmptyErrorsArray } from "./shared"; 16 | 17 | var pkgRootDir = path.resolve(__dirname, "..", ".."); 18 | var tsTypes = fork([ 19 | esProposalsDef, 20 | typescriptDef, 21 | jsxDef, 22 | ]); 23 | 24 | const babelParserDir = path.resolve( 25 | pkgRootDir, "src", "test", "data", "babel-parser"); 26 | 27 | const babelTSFixturesDir = 28 | path.join(babelParserDir, "test", "fixtures", "typescript"); 29 | 30 | glob("**/input.ts", { 31 | cwd: babelTSFixturesDir, 32 | }, (error, files) => { 33 | if (error) { 34 | throw error; 35 | } 36 | 37 | if (files.length < 10) { 38 | throw new Error(`Unexpectedly few **/input.ts files matched (${ 39 | files.length 40 | }) in ${ 41 | babelTSFixturesDir 42 | }`); 43 | } 44 | 45 | describe("Whole-program validation for Babel TypeScript tests", function () { 46 | if (error) { 47 | throw error; 48 | } 49 | 50 | files.forEach((tsPath: any) => { 51 | const fullPath = path.join(babelTSFixturesDir, tsPath); 52 | const pkgRootRelPath = path.relative(pkgRootDir, fullPath); 53 | 54 | if ( 55 | fullPath.endsWith("/arrow-function/generic-tsx/input.ts") || 56 | fullPath.endsWith("/tsx/invalid-gt-arrow-like/input.ts") 57 | ) { 58 | (it.skip || it)("[SKIPPED] " + pkgRootRelPath, done => done()); 59 | return; 60 | } 61 | 62 | it("should validate " + pkgRootRelPath, function (done) { 63 | fs.readFile(fullPath, "utf8", function (error, code) { 64 | if (error) { 65 | done(error); 66 | } else try { 67 | const ast = tryParse(code, fullPath); 68 | if (ast) { 69 | const expected = readJSONOrNull( 70 | path.join(path.dirname(fullPath), "output.json")); 71 | 72 | if ( 73 | hasNonEmptyErrorsArray(ast) || 74 | hasNonEmptyErrorsArray(expected) 75 | ) { 76 | // Most parsing errors are checked this way, thanks to 77 | // errorRecovery: true. 78 | assert.deepEqual( 79 | ast.errors.map(normalizeErrorString), 80 | expected.errors.map(normalizeErrorString), 81 | ); 82 | } else if ( 83 | ast.program && 84 | // If there were parsing errors, there's a good chance the rest 85 | // of the parsed AST is not fully conformant with the Program 86 | // type. If this clause is commented out, only 8 tests fail (not 87 | // great, not terrible, TODO maybe worth looking into). 88 | !hasNonEmptyErrorsArray(ast) 89 | ) { 90 | tsTypes.namedTypes.Program.assert(ast.program, true); 91 | } 92 | } 93 | 94 | done(); 95 | } catch (e) { 96 | done(e); 97 | } 98 | }); 99 | }); 100 | }); 101 | }); 102 | 103 | function readJSONOrNull(fullPath: string) { 104 | try { 105 | return JSON.parse(fs.readFileSync(fullPath).toString()); 106 | } catch { 107 | return null; 108 | } 109 | } 110 | 111 | function tryParse(code: string, fullPath: string) { 112 | const parseOptions = { 113 | errorRecovery: true, 114 | ...getOptions(fullPath), 115 | }; 116 | 117 | try { 118 | return babelParse(code, parseOptions); 119 | 120 | } catch (error: any) { 121 | // If parsing fails, check options.json to see if the failure was 122 | // expected. 123 | try { 124 | var options = JSON.parse(fs.readFileSync( 125 | path.join(path.dirname(fullPath), "options.json")).toString()); 126 | } catch (optionsError: any) { 127 | console.error(optionsError.message); 128 | } 129 | 130 | if ( 131 | options && 132 | options.throws && 133 | normalizeErrorString(options.throws) === 134 | normalizeErrorString(error.message) 135 | ) { 136 | return null; 137 | } 138 | 139 | throw error; 140 | } 141 | } 142 | 143 | function normalizeErrorString(error: ParseError | Error | string) { 144 | // Sometimes the line or column numbers are slightly off for catastrophic 145 | // parse errors. TODO Investigate why this is necessary. 146 | return String(error).replace(/\(\d+:\d+\)/g, "(line:column)"); 147 | } 148 | 149 | function getOptions(fullPath: string): ParserOptions { 150 | var plugins = getPlugins(path.dirname(fullPath)); 151 | return { 152 | sourceType: "module", 153 | plugins, 154 | }; 155 | } 156 | 157 | function getPlugins(dir: string): ParserPlugin[] { 158 | try { 159 | var options = JSON.parse(fs.readFileSync( 160 | path.join(dir, "options.json") 161 | ).toString()); 162 | } catch (ignored) { 163 | options = {}; 164 | } 165 | 166 | if (options.plugins) { 167 | return options.plugins; 168 | } 169 | 170 | if (dir !== babelTSFixturesDir) { 171 | return getPlugins(path.dirname(dir)); 172 | } 173 | 174 | return [ 175 | "typescript", 176 | ]; 177 | } 178 | }); 179 | 180 | var tsCompilerDir = path.resolve( 181 | pkgRootDir, "src", "test", "data", "typescript-compiler"); 182 | 183 | glob("**/*.ts", { 184 | cwd: tsCompilerDir, 185 | }, (error, files) => { 186 | if (error) { 187 | throw error; 188 | } 189 | 190 | if (files.length < 10) { 191 | throw new Error(`Unexpectedly few **/*.ts files matched (${ 192 | files.length 193 | }) in ${ 194 | tsCompilerDir 195 | }`); 196 | } 197 | 198 | describe("Whole-program validation for TypeScript codebase", function () { 199 | if (error) { 200 | throw error; 201 | } 202 | 203 | this.timeout(20000); 204 | 205 | files.forEach((tsPath: string) => { 206 | var fullPath = path.join(tsCompilerDir, tsPath); 207 | 208 | // We have to skip checker.ts because of a bug in babel's typescript 209 | // parser plugin. See 210 | // https://github.com/babel/babel/issues/7235#issuecomment-549437974 211 | if (tsPath === "checker.ts") { 212 | return; 213 | } 214 | 215 | it("should validate " + path.relative(pkgRootDir, fullPath), function (done) { 216 | fs.readFile(fullPath, "utf8", function (error, code) { 217 | if (error) { 218 | done(error); 219 | } else try { 220 | var program = babelParse(code, { 221 | sourceType: "module", 222 | plugins: [ 223 | "typescript", 224 | "objectRestSpread", 225 | "classProperties", 226 | "optionalCatchBinding", 227 | "numericSeparator", 228 | "optionalChaining", 229 | "nullishCoalescingOperator", 230 | ] 231 | }).program; 232 | 233 | tsTypes.namedTypes.Program.assert(program, true); 234 | 235 | done(); 236 | } catch (e) { 237 | done(e); 238 | } 239 | }); 240 | }); 241 | }); 242 | }); 243 | 244 | describe('scope', () => { 245 | const scope = [ 246 | "type Foo = {}", 247 | "interface Bar {}" 248 | ]; 249 | 250 | const ast = babelParse(scope.join("\n"), { 251 | plugins: ['typescript'] 252 | }); 253 | 254 | it("should register typescript types with the scope", function() { 255 | visit(ast, { 256 | visitProgram(path) { 257 | assert(path.scope.declaresType('Foo')); 258 | assert(path.scope.declaresType('Bar')); 259 | assert.equal(path.scope.lookupType('Foo').getTypes()['Foo'][0].parent.node.type, 'TSTypeAliasDeclaration'); 260 | assert.equal(path.scope.lookupType('Bar').getTypes()['Bar'][0].parent.node.type, 'TSInterfaceDeclaration'); 261 | return false; 262 | } 263 | }); 264 | }); 265 | }); 266 | 267 | function assertVisited(node: ASTNode, visitors: Visitor): any { 268 | const visitedSet: Set = new Set(); 269 | const wrappedVisitors: Visitor = {} 270 | for (const _key of Object.keys(visitors)) { 271 | const key = _key as keyof Visitor 272 | wrappedVisitors[key] = function (this: Context, path: NodePath) { 273 | visitedSet.add(key); 274 | (visitors[key] as any)?.call(this, path) 275 | } 276 | } 277 | tsTypes.visit(node, wrappedVisitors); 278 | 279 | for (const key of Object.keys(visitors)) { 280 | assert.equal(visitedSet.has(key), true); 281 | } 282 | } 283 | 284 | describe('typescript types', () => { 285 | it("issue #294 - function declarations", function () { 286 | const program = babelParse([ 287 | "function foo(): T { }", 288 | "let bar: T", 289 | ].join("\n"), 290 | { plugins: ['typescript'] } 291 | ) 292 | 293 | assertVisited(program, { 294 | visitFunctionDeclaration(path) { 295 | assert.ok(path.scope.lookupType('T')); 296 | this.traverse(path); 297 | }, 298 | visitVariableDeclarator(path) { 299 | assert.equal(path.scope.lookupType('T'), null); 300 | this.traverse(path); 301 | } 302 | }); 303 | }); 304 | 305 | it("issue #294 - function expressions", function () { 306 | const program = babelParse([ 307 | "const foo = function (): T { }", 308 | "let bar: T", 309 | ].join("\n"), { 310 | plugins: ["typescript"] 311 | }); 312 | 313 | assertVisited(program, { 314 | visitFunctionExpression(path) { 315 | assert.ok(path.scope.lookupType('T')); 316 | this.traverse(path); 317 | }, 318 | visitVariableDeclarator(path) { 319 | if (path.node.id.type === 'Identifier' && path.node.id.name === 'bar') { 320 | assert.equal(path.scope.lookupType('T'), null); 321 | } 322 | this.traverse(path); 323 | } 324 | }); 325 | }); 326 | 327 | it("issue #294 - arrow function expressions", function () { 328 | const program = babelParse([ 329 | "const foo = (): T => { }", 330 | "let bar: T" 331 | ].join("\n"), { 332 | plugins: ["typescript"] 333 | }); 334 | 335 | assertVisited(program, { 336 | visitArrowFunctionExpression(path) { 337 | assert.ok(path.scope.lookupType('T')); 338 | this.traverse(path); 339 | }, 340 | visitVariableDeclarator(path) { 341 | assert.equal(path.scope.lookupType('T'), null); 342 | this.traverse(path); 343 | } 344 | }); 345 | }); 346 | 347 | it("issue #294 - class declarations", function () { 348 | const program = babelParse([ 349 | "class Foo extends Bar> { }", 350 | "let bar: T" 351 | ].join("\n"), { 352 | plugins: ["typescript"] 353 | }); 354 | 355 | assertVisited(program, { 356 | visitTSTypeParameterInstantiation(path) { 357 | assert.ok(path.scope.lookupType('T')); 358 | this.traverse(path); 359 | }, 360 | visitVariableDeclarator(path) { 361 | assert.equal(path.scope.lookupType('T'), null); 362 | this.traverse(path); 363 | } 364 | }); 365 | }); 366 | 367 | it("issue #294 - class expressions", function () { 368 | const program = babelParse([ 369 | "const foo = class Foo extends Bar> { }", 370 | "let bar: T" 371 | ].join("\n"), { 372 | plugins: ["typescript"] 373 | }); 374 | 375 | assertVisited(program, { 376 | visitTSTypeParameterInstantiation(path) { 377 | assert.ok(path.scope.lookupType('T')); 378 | this.traverse(path); 379 | }, 380 | visitVariableDeclarator(path) { 381 | if (path.node.id.type === 'Identifier' && path.node.id.name === 'bar') { 382 | assert.equal(path.scope.lookupType('T'), null); 383 | assert.equal(path.scope.lookupType('Foo'), null); 384 | } 385 | this.traverse(path); 386 | } 387 | }); 388 | }); 389 | 390 | it("issue #296 - interface declarations", function () { 391 | const program = babelParse([ 392 | "interface Foo extends Bar> { }", 393 | "let bar: T" 394 | ].join("\n"), { 395 | plugins: ["typescript"] 396 | }); 397 | 398 | assertVisited(program, { 399 | visitTSTypeParameterInstantiation(path) { 400 | assert.ok(path.scope.lookupType('T')); 401 | this.traverse(path); 402 | }, 403 | visitVariableDeclarator(path) { 404 | assert.equal(path.scope.lookupType('T'), null); 405 | assert.ok(path.scope.lookupType('Foo')); 406 | this.traverse(path); 407 | } 408 | }); 409 | }); 410 | }); 411 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./lib", 5 | "target": "es5", 6 | "module": "commonjs", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "moduleResolution": "node", 14 | "esModuleInterop": true, 15 | "importHelpers": true, 16 | "stripInternal": true, 17 | "lib": [ 18 | "es2015" 19 | ], 20 | "types": [ 21 | "mocha" 22 | ] 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "types/*.d.ts", 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "src/test/data" 31 | ] 32 | } 33 | --------------------------------------------------------------------------------