diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 00000000..09115c86 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json.schemastore.org/mocharc.json", + "require": "tsx" + } diff --git a/package.json b/package.json index 620f3506..1f18f1e6 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "scripts": { "gen": "ts-node --transpile-only script/gen-types.ts", "test": "npm run gen && npm run build && script/run-tests.sh", - "run-test": "mocha --reporter spec --full-trace lib/test/run.js", + "test:watch": "mocha --reporter spec --full-trace src/test/run.ts --watch --watch-files src", + "run-test": "npm run gen && npm run build && mocha --reporter spec --full-trace lib/test/run.js", "clean": "rimraf lib/", "build": "tsc", "prepare": "npm run clean && npm run build" @@ -63,6 +64,7 @@ "reify": "0.20.12", "rimraf": "6.0.1", "ts-node": "10.9.2", + "tsx": "^4.16.2", "typescript": "5.5.3" }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9745acc..bac3b8d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,6 +58,9 @@ devDependencies: ts-node: specifier: 10.9.2 version: 10.9.2(@types/node@16.11.10)(typescript@5.5.3) + tsx: + specifier: ^4.16.2 + version: 4.16.2 typescript: specifier: 5.5.3 version: 5.5.3 @@ -98,6 +101,213 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -432,6 +642,37 @@ packages: resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} dev: true + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -519,6 +760,14 @@ packages: dev: true optional: true + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true @@ -536,6 +785,12 @@ packages: has-symbols: 1.0.3 dev: true + /get-tsconfig@4.7.5: + resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -944,6 +1199,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /rimraf@6.0.1: resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} engines: {node: 20 || >=22} @@ -1092,6 +1351,17 @@ packages: /tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + /tsx@4.16.2: + resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.21.5 + get-tsconfig: 4.7.5 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /typescript@5.5.3: resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} diff --git a/src/node-path.ts b/src/node-path.ts index 6b2317b7..b5082fab 100644 --- a/src/node-path.ts +++ b/src/node-path.ts @@ -139,7 +139,7 @@ export default function nodePathPlugin(fork: Fork): NodePathConstructor { var scope = pp && pp.scope; if (n.Node.check(value) && - Scope.isEstablishedBy(value)) { + Scope.isEstablishedBy(this)) { scope = new Scope(this, scope); } diff --git a/src/scope.ts b/src/scope.ts index b285c588..78896a49 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -7,11 +7,14 @@ import typesPlugin from "./types"; var hasOwn = Object.prototype.hasOwnProperty; +export type ScopeType = "global" | "function" | "block" | "switch" | "catch" | "with" | "for" | "class" + export type ScopeBinding = Record; export type ScopeTypes = Record; export interface Scope { + type: ScopeType; path: NodePath; node: NodePath['value']; isGlobal: boolean; @@ -31,6 +34,11 @@ export interface Scope { lookup(name: string): Scope; lookupType(name: string): Scope; getGlobalScope(): Scope | null; + + // private methods + scanScope: (path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) => void; + recursiveScanScope: (path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) => void; + recursiveScanChild: (path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) => void; } export interface ScopeConstructor { @@ -51,8 +59,8 @@ export default function scopePlugin(fork: Fork) { if (!(this instanceof Scope)) { throw new Error("Scope constructor cannot be invoked without 'new'"); } - if (!TypeParameterScopeType.check(path.value)) { - ScopeType.assert(path.value); + if (!TypeParameterScopeType.check(path.value) && !ScopeType.check(path.value) && !isForScopeType(path.value)) { + throw new Error("Invalid scope node type " + (path.value as any).type); } var depth: number; @@ -67,7 +75,10 @@ export default function scopePlugin(fork: Fork) { depth = 0; } + var type = getScopeType(path); + Object.defineProperties(this, { + type: { value: type }, path: { value: path }, node: { value: path.value }, isGlobal: { value: !parentScope, enumerable: true }, @@ -88,9 +99,28 @@ export default function scopePlugin(fork: Fork) { // In case you didn't know, the caught parameter shadows any variable // of the same name in an outer scope. - namedTypes.CatchClause + namedTypes.CatchClause, + + namedTypes.BlockStatement, ); + var ForLoopType = Type.or( + namedTypes.ForStatement, + namedTypes.ForInStatement, + namedTypes.ForOfStatement + ); + + var isForScopeType = function isScopeType(node: NodePath['node']) { + if (ForLoopType.check(node)) { + const variableDeclarator = node.init || node.left; + return variableDeclarator + && namedTypes.VariableDeclaration.check(variableDeclarator) + && variableDeclarator.kind !== "var"; + } + + return false; + } + // These types introduce scopes that are restricted to type parameters in // Flow (this doesn't apply to ECMAScript). var TypeParameterScopeType = Type.or( @@ -108,13 +138,18 @@ export default function scopePlugin(fork: Fork) { namedTypes.TSTypeParameter, ); - Scope.isEstablishedBy = function(node: NodePath['node']) { - return ScopeType.check(node) || TypeParameterScopeType.check(node); + Scope.isEstablishedBy = function(path: NodePath) { + const node = path.value; + if (namedTypes.BlockStatement.check(node) && namedTypes.Function.check(path.parentPath.value)) { + return false; + } + + return ScopeType.check(node) || TypeParameterScopeType.check(node) || isForScopeType(node); }; var Sp: Scope = Scope.prototype; -// Will be overridden after an instance lazily calls scanScope. + // Will be overridden after an instance lazily calls scanScope. Sp.didScan = false; Sp.declares = function(name) { @@ -185,7 +220,7 @@ export default function scopePlugin(fork: Fork) { // Empty out this.types, just in cases. delete this.types[name]; } - scanScope(this.path, this.bindings, this.types); + this.scanScope(this.path, this.bindings, this.types); this.didScan = true; } }; @@ -200,7 +235,57 @@ export default function scopePlugin(fork: Fork) { return this.types; }; - function scanScope(path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) { + function getScopeType(path: NodePath): ScopeType { + const node = path.value; + if (namedTypes.Program.check(node)) { + return "global"; + } + + if (namedTypes.Function.check(node)) { + return "function"; + } + + if (namedTypes.BlockStatement.check(node)) { + return "block"; + } + + if (namedTypes.SwitchStatement.check(node)) { + return "switch"; + } + + if (namedTypes.CatchClause.check(node)) { + return "catch"; + } + + if (namedTypes.WithStatement.check(node)) { + return "with"; + } + + if (ForLoopType.check(node)) { + return "for"; + } + + if (namedTypes.ClassDeclaration.check(node) || namedTypes.ClassExpression.check(node)) { + return "class"; + } + + // @ts-ignore + throw new Error("Cannot determine ScopeType for node " + node.type); + } + + function getVariableScope(scope: Scope) { + if (!scope.parent) return scope; + + const variableScopeTypes = ['global', 'module', 'function', 'class-field-initializer', 'class-static-block']; + if (variableScopeTypes.includes(scope.type)) { + return scope; + } + + return getVariableScope(scope.parent); + } + + Sp.scanScope = function (path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) { + var node = path.value; if (TypeParameterScopeType.check(node)) { const params = path.get('typeParameters', 'params'); @@ -217,35 +302,45 @@ export default function scopePlugin(fork: Fork) { // declarations create bindings in the outer scope. addPattern(path.get("param"), bindings); } else { - recursiveScanScope(path, bindings, scopeTypes); + this.recursiveScanScope(path, bindings, scopeTypes); } } + + if (isForScopeType(node)) { + this.recursiveScanScope(path, bindings, scopeTypes); + } } - function recursiveScanScope(path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) { + Sp.recursiveScanScope = function (path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) { var node = path.value; - if (path.parent && - namedTypes.FunctionExpression.check(path.parent.node) && - path.parent.node.id) { - addPattern(path.parent.get("id"), bindings); - } - if (!node) { // None of the remaining cases matter if node is falsy. } else if (isArray.check(node)) { path.each((childPath: NodePath) => { - recursiveScanChild(childPath, bindings, scopeTypes); + this.recursiveScanChild(childPath, bindings, scopeTypes); }); } else if (namedTypes.Function.check(node)) { + if (namedTypes.FunctionExpression.check(node) && node.id) { + addPattern(path.get("id"), bindings); + } + path.get("params").each((paramPath: NodePath) => { addPattern(paramPath, bindings); }); - recursiveScanChild(path.get("body"), bindings, scopeTypes); - recursiveScanScope(path.get("typeParameters"), bindings, scopeTypes); + const functionBody = path.get("body"); + if (namedTypes.BlockStatement.check(functionBody.value)) { + functionBody.get("body").each((childPath: NodePath) => { + this.recursiveScanChild(childPath, bindings, scopeTypes); + }); + } else { + this.recursiveScanChild(functionBody, bindings, scopeTypes); + } + + this.recursiveScanScope(path.get("typeParameters"), bindings, scopeTypes); } else if ( (namedTypes.TypeAlias && namedTypes.TypeAlias.check(node)) || @@ -256,8 +351,15 @@ export default function scopePlugin(fork: Fork) { addTypePattern(path.get("id"), scopeTypes); } else if (namedTypes.VariableDeclarator.check(node)) { - addPattern(path.get("id"), bindings); - recursiveScanChild(path.get("init"), bindings, scopeTypes); + if (path.parent && namedTypes.VariableDeclaration.check(path.parent.node)) { + var variableTargetScope = path.parent.node.kind === "var" ? getVariableScope(this) : this; + var bd = variableTargetScope === this ? bindings : variableTargetScope.getBindings(); + addPattern(path.get("id"), bd); + this.recursiveScanChild(path.get("init"), bd, scopeTypes); + } else { + addPattern(path.get("id"), bindings); + this.recursiveScanChild(path.get("init"), bindings, scopeTypes); + } } else if (node.type === "ImportSpecifier" || node.type === "ImportNamespaceSpecifier" || @@ -272,13 +374,18 @@ export default function scopePlugin(fork: Fork) { bindings ); + } else if (namedTypes.BlockStatement.check(node.value)) { + path.get("body").each((childPath: NodePath) => { + this.recursiveScanChild(childPath, bindings, scopeTypes); + }); + } else if (Node.check(node) && !Expression.check(node)) { - types.eachField(node, function(name: any, child: any) { + types.eachField(node, (name: any, child: any) => { var childPath = path.get(name); if (!pathHasValue(childPath, child)) { throw new Error(""); } - recursiveScanChild(childPath, bindings, scopeTypes); + this.recursiveScanChild(childPath, bindings, scopeTypes); }); } } @@ -300,21 +407,20 @@ export default function scopePlugin(fork: Fork) { return false; } - function recursiveScanChild(path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) { + Sp.recursiveScanChild = function (path: NodePath, bindings: ScopeBinding, scopeTypes: ScopeTypes) { var node = path.value; if (!node || Expression.check(node)) { // Ignore falsy values and Expressions. - } else if (namedTypes.FunctionDeclaration.check(node) && - node.id !== null) { + } else if (namedTypes.FunctionDeclaration.check(node) && node.id !== null) { addPattern(path.get("id"), bindings); } else if (namedTypes.ClassDeclaration && namedTypes.ClassDeclaration.check(node) && node.id !== null) { addPattern(path.get("id"), bindings); - recursiveScanScope(path.get("typeParameters"), bindings, scopeTypes); + this.recursiveScanScope(path.get("typeParameters"), bindings, scopeTypes); } else if ( (namedTypes.InterfaceDeclaration && @@ -324,7 +430,7 @@ export default function scopePlugin(fork: Fork) { ) { addTypePattern(path.get("id"), scopeTypes); - } else if (ScopeType.check(node)) { + } else if (ScopeType.check(node) || isForScopeType(node)) { if ( namedTypes.CatchClause.check(node) && // TODO Broaden this to accept any pattern. @@ -336,7 +442,7 @@ export default function scopePlugin(fork: Fork) { // Any declarations that occur inside the catch body that do // not have the same name as the catch parameter should count // as bindings in the outer scope. - recursiveScanScope(path.get("body"), bindings, scopeTypes); + this.recursiveScanScope(path.get("body"), bindings, scopeTypes); // If a new binding matching the catch parameter name was // created while scanning the catch body, ignore it because it @@ -348,7 +454,7 @@ export default function scopePlugin(fork: Fork) { } } else { - recursiveScanScope(path, bindings, scopeTypes); + this.recursiveScanScope(path, bindings, scopeTypes); } } @@ -371,7 +477,7 @@ export default function scopePlugin(fork: Fork) { namedTypes.ObjectPattern && namedTypes.ObjectPattern.check(pattern) ) { - patternPath.get('properties').each(function(propertyPath: NodePath) { + patternPath.get('properties').each((propertyPath: NodePath) => { var property = propertyPath.value; if (namedTypes.Pattern.check(property)) { addPattern(propertyPath, bindings); @@ -393,7 +499,7 @@ export default function scopePlugin(fork: Fork) { namedTypes.ArrayPattern && namedTypes.ArrayPattern.check(pattern) ) { - patternPath.get('elements').each(function(elementPath: NodePath) { + patternPath.get('elements').each((elementPath: NodePath) => { var element = elementPath.value; if (namedTypes.Pattern.check(element)) { addPattern(elementPath, bindings); diff --git a/src/test/ecmascript.ts b/src/test/ecmascript.ts index a4f3ec86..b37fed38 100644 --- a/src/test/ecmascript.ts +++ b/src/test/ecmascript.ts @@ -1373,6 +1373,7 @@ describe("catch block scope", function() { " bar();", " } catch (e) {", " var f = e + 1;", + " let h = e + 2;", " return function(g) {", " return e + g;", " };", @@ -1386,6 +1387,8 @@ describe("catch block scope", function() { var fooScope = fooPath.scope; var catchPath = fooPath.get("body", "body", 0, "handler"); var catchScope = catchPath.scope; + var catchBlockPath = catchPath.get("body"); + var catchBlockScope = catchBlockPath.scope; it("should not affect outer scope declarations", function() { n.FunctionDeclaration.assert(fooScope.node); @@ -1400,10 +1403,11 @@ describe("catch block scope", function() { assert.strictEqual(catchScope.declares("f"), false); assert.strictEqual(catchScope.lookup("e"), catchScope); assert.strictEqual(catchScope.lookup("f"), fooScope); + assert.strictEqual(catchBlockScope.lookup("h"), catchBlockScope); }); it("should shadow only the parameter in nested scopes", function() { - var closurePath = catchPath.get("body", "body", 1, "argument"); + var closurePath = catchPath.get("body", "body", 2, "argument"); var closureScope = closurePath.scope; n.FunctionExpression.assert(closureScope.node); assert.strictEqual(closureScope.declares("e"), false);