Skip to content

Commit cab9df6

Browse files
committed
.
0 parents  commit cab9df6

File tree

16 files changed

+1016
-0
lines changed

16 files changed

+1016
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true

.github/workflows/bb.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
jobs:
2+
main:
3+
runs-on: ubuntu-latest
4+
steps:
5+
- uses: unifiedjs/beep-boop-beta@main
6+
with:
7+
repo-token: ${{secrets.GITHUB_TOKEN}}
8+
name: bb
9+
on:
10+
issues:
11+
types: [closed, edited, labeled, opened, reopened, unlabeled]
12+
pull_request_target:
13+
types: [closed, edited, labeled, opened, reopened, unlabeled]

.github/workflows/main.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
jobs:
2+
main:
3+
name: ${{matrix.node}}
4+
runs-on: ubuntu-latest
5+
steps:
6+
- uses: actions/checkout@v4
7+
- uses: actions/setup-node@v4
8+
with:
9+
node-version: ${{matrix.node}}
10+
- run: npm install
11+
- run: npm test
12+
- uses: codecov/codecov-action@v4
13+
strategy:
14+
matrix:
15+
node:
16+
- lts/hydrogen
17+
- node
18+
name: main
19+
on:
20+
- pull_request
21+
- push

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.DS_Store
2+
*.d.ts.map
3+
*.d.ts
4+
*.log
5+
coverage/
6+
node_modules/
7+
yarn.lock
8+
!/lib/types.d.ts
9+
!/index.d.ts

.npmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ignore-scripts=true
2+
package-lock=false

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
coverage/
2+
*.md

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type {Scope, Visitors} from './lib/types.js'
2+
export {createVisitors} from './lib/index.js'

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Note: types exposed from `index.d.ts`.
2+
export {createVisitors} from './lib/index.js'

lib/index.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* @import {Node, Pattern} from 'estree'
3+
* @import {Scope, Visitors} from './types.js'
4+
*/
5+
6+
import {ok as assert} from 'devlop'
7+
8+
/**
9+
* Create state to track what’s defined.
10+
*
11+
* @returns {Visitors}
12+
* State.
13+
*/
14+
export function createVisitors() {
15+
/** @type {[topLevel: Scope, ...rest: Array<Scope>]} */
16+
const scopes = [{block: false, defined: []}]
17+
18+
return {enter, exit, scopes}
19+
20+
/**
21+
* @param {Node} node
22+
* Node.
23+
* @returns {undefined}
24+
* Nothing.
25+
*/
26+
function enter(node) {
27+
// On arrow functions, create scope, add parameters.
28+
if (node.type === 'ArrowFunctionExpression') {
29+
scopes.push({block: false, defined: []})
30+
31+
for (const parameter of node.params) {
32+
definePattern(parameter, false)
33+
}
34+
}
35+
// On block statements, create scope.
36+
// Not sure why `periscopic` only does `Block`/`For`/`ForIn`/`ForOf`.
37+
// I added `DoWhile`/`While` here just to be sure.
38+
else if (
39+
node.type === 'BlockStatement' ||
40+
node.type === 'DoWhileStatement' ||
41+
node.type === 'ForInStatement' ||
42+
node.type === 'ForOfStatement' ||
43+
node.type === 'ForStatement' ||
44+
node.type === 'WhileStatement'
45+
) {
46+
scopes.push({block: true, defined: []})
47+
}
48+
49+
// On catch clauses, create scope, add param.
50+
else if (node.type === 'CatchClause') {
51+
scopes.push({block: true, defined: []})
52+
if (node.param) definePattern(node.param, true)
53+
}
54+
55+
// Add identifier of class declaration.
56+
else if (node.type === 'ClassDeclaration') {
57+
defineIdentifier(node.id.name, false)
58+
}
59+
60+
// On function declarations, add name, create scope, add parameters.
61+
else if (node.type === 'FunctionDeclaration') {
62+
defineIdentifier(node.id.name, false)
63+
scopes.push({block: false, defined: []})
64+
65+
for (const parameter of node.params) {
66+
definePattern(parameter, false)
67+
}
68+
}
69+
70+
// On function expressions, add name, create scope, add parameters.
71+
else if (node.type === 'FunctionExpression') {
72+
if (node.id) defineIdentifier(node.id.name, false)
73+
scopes.push({block: false, defined: []})
74+
75+
for (const parameter of node.params) {
76+
definePattern(parameter, false)
77+
}
78+
}
79+
80+
// Add specifiers of import declarations.
81+
else if (node.type === 'ImportDeclaration') {
82+
for (const specifier of node.specifiers) {
83+
defineIdentifier(specifier.local.name, false)
84+
}
85+
}
86+
87+
// Add patterns of variable declarations.
88+
else if (node.type === 'VariableDeclaration') {
89+
for (const declaration of node.declarations) {
90+
definePattern(declaration.id, node.kind !== 'var')
91+
}
92+
}
93+
}
94+
95+
/**
96+
* @param {Node} node
97+
* Node.
98+
* @returns {undefined}
99+
* Nothing.
100+
*/
101+
function exit(node) {
102+
if (
103+
node.type === 'ArrowFunctionExpression' ||
104+
node.type === 'FunctionDeclaration' ||
105+
node.type === 'FunctionExpression'
106+
) {
107+
const scope = scopes.pop()
108+
assert(scope, 'expected scope')
109+
assert(!scope.block, 'expected non-block')
110+
} else if (
111+
node.type === 'BlockStatement' ||
112+
node.type === 'CatchClause' ||
113+
node.type === 'DoWhileStatement' ||
114+
node.type === 'ForInStatement' ||
115+
node.type === 'ForOfStatement' ||
116+
node.type === 'ForStatement' ||
117+
node.type === 'WhileStatement'
118+
) {
119+
const scope = scopes.pop()
120+
assert(scope, 'expected scope')
121+
assert(scope.block, 'expected block')
122+
}
123+
}
124+
125+
/**
126+
* Define an identifier in a scope.
127+
*
128+
* @param {string} id
129+
* @param {boolean} block
130+
* @returns {undefined}
131+
*/
132+
function defineIdentifier(id, block) {
133+
let index = scopes.length
134+
/** @type {Scope | undefined} */
135+
let scope
136+
137+
while (index--) {
138+
scope = scopes[index]
139+
140+
if (block || !scope.block) {
141+
break
142+
}
143+
}
144+
145+
assert(scope)
146+
scope.defined.push(id)
147+
}
148+
149+
/**
150+
* Define a pattern in a scope.
151+
*
152+
* @param {Pattern} pattern
153+
* @param {boolean} block
154+
*/
155+
function definePattern(pattern, block) {
156+
// `[, x]`
157+
if (pattern.type === 'ArrayPattern') {
158+
for (const element of pattern.elements) {
159+
if (element) {
160+
definePattern(element, block)
161+
}
162+
}
163+
}
164+
165+
// `{x=y}`
166+
else if (pattern.type === 'AssignmentPattern') {
167+
definePattern(pattern.left, block)
168+
}
169+
170+
// `x`
171+
else if (pattern.type === 'Identifier') {
172+
defineIdentifier(pattern.name, block)
173+
}
174+
175+
// `{x}`
176+
else if (pattern.type === 'ObjectPattern') {
177+
for (const property of pattern.properties) {
178+
// `{key}`, `{key = value}`, `{key: value}`
179+
if (property.type === 'Property') {
180+
definePattern(property.value, block)
181+
}
182+
// `{...x}`
183+
else {
184+
assert(property.type === 'RestElement')
185+
definePattern(property, block)
186+
}
187+
}
188+
}
189+
190+
// `...x`
191+
else {
192+
assert(pattern.type === 'RestElement')
193+
definePattern(pattern.argument, block)
194+
}
195+
}
196+
}

lib/types.d.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type {Node} from 'estree'
2+
3+
/**
4+
* Scope.
5+
*/
6+
export interface Scope {
7+
/**
8+
* Whether this is a block scope or not;
9+
* blocks are things made by `for` and `try` and `if`;
10+
* non-blocks are functions and the top-level scope.
11+
*/
12+
block: boolean
13+
/**
14+
* Identifiers that are defined in this scope.
15+
*/
16+
defined: Array<string>
17+
}
18+
19+
/**
20+
* State to track what’s defined;
21+
* contains `enter`, `exit` callbacks you must call and `scopes`.
22+
*/
23+
export interface Visitors {
24+
/**
25+
* List of scopes;
26+
* the first scope is the top-level scope;
27+
* the last scope is the current scope.
28+
*/
29+
scopes: [topLevel: Scope, ...rest: Array<Scope>]
30+
/**
31+
* Callback you must call when entering a node.
32+
*
33+
* @param node
34+
* Node.
35+
* @returns
36+
* Nothing.
37+
*/
38+
enter(node: Node): undefined
39+
/**
40+
* Callback you must call when exiting (leaving) a node.
41+
*
42+
* @param node
43+
* Node.
44+
* @returns
45+
* Nothing.
46+
*/
47+
exit(node: Node): undefined
48+
}

0 commit comments

Comments
 (0)