Skip to content

Commit d306f87

Browse files
committedAug 29, 2024··
Replace periscopic with estree-util-scope
1 parent 2ecebb0 commit d306f87

File tree

7 files changed

+170
-184
lines changed

7 files changed

+170
-184
lines changed
 

Diff for: ‎package-lock.json

+64-75
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
"eslint-plugin-react": "^7.0.0",
5656
"eslint-plugin-react-hooks": "^4.0.0",
5757
"estree-to-babel": "^9.0.0",
58+
"estree-util-scope": "^1.0.0",
5859
"estree-util-value-to-estree": "^3.0.0",
60+
"estree-walker": "^3.0.0",
5961
"globby": "^14.0.0",
6062
"hast-util-from-html": "^2.0.0",
6163
"hast-util-sanitize": "^5.0.0",
@@ -67,7 +69,6 @@
6769
"ink": "^5.0.0",
6870
"lz-string": "^1.0.0",
6971
"p-all": "^5.0.0",
70-
"periscopic": "^3.0.0",
7172
"postcss": "^8.0.0",
7273
"postcss-cli": "^11.0.0",
7374
"preact": "^10.0.0",

Diff for: ‎packages/mdx/lib/plugin/recma-document.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
*/
3131

3232
import {ok as assert} from 'devlop'
33+
import {createVisitors} from 'estree-util-scope'
3334
import {walk} from 'estree-walker'
34-
import {analyze} from 'periscopic'
3535
import {positionFromEstree} from 'unist-util-position-from-estree'
3636
import {stringifyPosition} from 'unist-util-stringify-position'
3737
import {create} from '../util/estree-util-create.js'
@@ -433,9 +433,24 @@ export function recmaDocument(options) {
433433
// export var a = 1
434434
// ```
435435
if (node.declaration) {
436-
exportedIdentifiers.push(
437-
...analyze(node.declaration).scope.declarations.keys()
438-
)
436+
const visitors = createVisitors()
437+
// Walk the top-level scope.
438+
walk(node, {
439+
enter(node) {
440+
visitors.enter(node)
441+
442+
if (
443+
node.type === 'ArrowFunctionExpression' ||
444+
node.type === 'FunctionDeclaration' ||
445+
node.type === 'FunctionExpression'
446+
) {
447+
this.skip()
448+
visitors.exit(node)
449+
}
450+
},
451+
leave: visitors.exit
452+
})
453+
exportedIdentifiers.push(...visitors.scopes[0].defined)
439454
}
440455

441456
// ```tsx

Diff for: ‎packages/mdx/lib/plugin/recma-jsx-rewrite.js

+64-91
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,19 @@
66
ImportSpecifier,
77
JSXElement,
88
ModuleDeclaration,
9-
Node,
109
ObjectPattern,
1110
Program,
1211
Property,
1312
SpreadElement,
1413
Statement,
1514
VariableDeclarator
1615
* } from 'estree-jsx'
17-
* @import {Scope as PeriscopicScope} from 'periscopic'
16+
* @import {Scope} from 'estree-util-scope'
1817
* @import {VFile} from 'vfile'
1918
* @import {ProcessorOptions} from '../core.js'
2019
*/
2120

2221
/**
23-
* @typedef {PeriscopicScope & {node: Node}} Scope
24-
* Scope (with a `node`).
25-
*
2622
* @typedef StackEntry
2723
* Entry.
2824
* @property {Array<string>} components
@@ -40,8 +36,8 @@
4036
*/
4137

4238
import {name as isIdentifierName} from 'estree-util-is-identifier-name'
39+
import {createVisitors} from 'estree-util-scope'
4340
import {walk} from 'estree-walker'
44-
import {analyze} from 'periscopic'
4541
import {stringifyPosition} from 'unist-util-stringify-position'
4642
import {positionFromEstree} from 'unist-util-position-from-estree'
4743
import {specifiersToDeclarations} from '../util/estree-util-specifiers-to-declarations.js'
@@ -75,21 +71,15 @@ export function recmaJsxRewrite(options) {
7571
* Nothing.
7672
*/
7773
return function (tree, file) {
78-
// Find everything that’s defined in the top-level scope.
79-
const scopeInfo = analyze(tree)
74+
const visitors = createVisitors()
8075
/** @type {Array<StackEntry>} */
8176
const functionStack = []
8277
let importProvider = false
8378
let createErrorHelper = false
84-
/** @type {Scope | undefined} */
85-
let currentScope
8679

8780
walk(tree, {
8881
enter(node) {
89-
// Cast because we match `node`.
90-
const newScope = /** @type {Scope | undefined} */ (
91-
scopeInfo.map.get(node)
92-
)
82+
visitors.enter(node)
9383

9484
if (
9585
node.type === 'FunctionDeclaration' ||
@@ -105,31 +95,26 @@ export function recmaJsxRewrite(options) {
10595
tags: []
10696
})
10797

108-
// MDXContent only ever contains MDXLayout
98+
// `MDXContent` only ever contains `MDXLayout`.
10999
if (
110100
isNamedFunction(node, 'MDXContent') &&
111-
newScope &&
112-
!inScope(newScope, 'MDXLayout')
101+
!inScope(visitors.scopes, 'MDXLayout')
113102
) {
114103
functionStack[0].components.push('MDXLayout')
115104
}
116105
}
117106

118-
const functionScope = functionStack[0]
107+
const functionInfo = functionStack[0]
108+
119109
if (
120-
!functionScope ||
121-
(!isNamedFunction(functionScope.node, '_createMdxContent') &&
110+
!functionInfo ||
111+
(!isNamedFunction(functionInfo.node, '_createMdxContent') &&
122112
!providerImportSource)
123113
) {
124114
return
125115
}
126116

127-
if (newScope) {
128-
newScope.node = node
129-
currentScope = newScope
130-
}
131-
132-
if (currentScope && node.type === 'JSXElement') {
117+
if (node.type === 'JSXElement') {
133118
let name = node.openingElement.name
134119

135120
// `<x.y>`, `<Foo.Bar>`, `<x.y.z>`.
@@ -146,28 +131,22 @@ export function recmaJsxRewrite(options) {
146131
ids.unshift(name.name)
147132
const fullId = ids.join('.')
148133
const id = name.name
134+
const isInScope = inScope(visitors.scopes, id)
149135

150-
const isInScope = inScope(currentScope, id)
151-
152-
if (!Object.hasOwn(functionScope.references, fullId)) {
153-
// Cast because we match `node`.
154-
const parentScope = /** @type {Scope | undefined} */ (
155-
currentScope.parent
156-
)
157-
if (
158-
!isInScope ||
136+
if (
137+
!Object.hasOwn(functionInfo.references, fullId) &&
138+
(!isInScope ||
159139
// If the parent scope is `_createMdxContent`, then this
160140
// references a component we can add a check statement for.
161-
(parentScope &&
162-
parentScope.node.type === 'FunctionDeclaration' &&
163-
isNamedFunction(parentScope.node, '_createMdxContent'))
164-
) {
165-
functionScope.references[fullId] = {component: true, node}
166-
}
141+
(functionStack.length === 1 &&
142+
functionStack[0].node.type === 'FunctionDeclaration' &&
143+
isNamedFunction(functionStack[0].node, '_createMdxContent')))
144+
) {
145+
functionInfo.references[fullId] = {component: true, node}
167146
}
168147

169-
if (!functionScope.objects.includes(id) && !isInScope) {
170-
functionScope.objects.push(id)
148+
if (!functionInfo.objects.includes(id) && !isInScope) {
149+
functionInfo.objects.push(id)
171150
}
172151
}
173152
// `<xml:thing>`.
@@ -181,18 +160,18 @@ export function recmaJsxRewrite(options) {
181160
else if (isIdentifierName(name.name) && !/^[a-z]/.test(name.name)) {
182161
const id = name.name
183162

184-
if (!inScope(currentScope, id)) {
163+
if (!inScope(visitors.scopes, id)) {
185164
// No need to add an error for an undefined layout — we use an
186165
// `if` later.
187166
if (
188167
id !== 'MDXLayout' &&
189-
!Object.hasOwn(functionScope.references, id)
168+
!Object.hasOwn(functionInfo.references, id)
190169
) {
191-
functionScope.references[id] = {component: true, node}
170+
functionInfo.references[id] = {component: true, node}
192171
}
193172

194-
if (!functionScope.components.includes(id)) {
195-
functionScope.components.push(id)
173+
if (!functionInfo.components.includes(id)) {
174+
functionInfo.components.push(id)
196175
}
197176
}
198177
} else if (node.data && node.data._mdxExplicitJsx) {
@@ -202,18 +181,18 @@ export function recmaJsxRewrite(options) {
202181
} else {
203182
const id = name.name
204183

205-
if (!functionScope.tags.includes(id)) {
206-
functionScope.tags.push(id)
184+
if (!functionInfo.tags.includes(id)) {
185+
functionInfo.tags.push(id)
207186
}
208187

209188
/** @type {Array<number | string>} */
210189
let jsxIdExpression = ['_components', id]
211190
if (isIdentifierName(id) === false) {
212191
let invalidComponentName =
213-
functionScope.idToInvalidComponentName.get(id)
192+
functionInfo.idToInvalidComponentName.get(id)
214193
if (invalidComponentName === undefined) {
215-
invalidComponentName = `_component${functionScope.idToInvalidComponentName.size}`
216-
functionScope.idToInvalidComponentName.set(
194+
invalidComponentName = `_component${functionInfo.idToInvalidComponentName.size}`
195+
functionInfo.idToInvalidComponentName.set(
217196
id,
218197
invalidComponentName
219198
)
@@ -233,6 +212,8 @@ export function recmaJsxRewrite(options) {
233212
}
234213
},
235214
leave(node) {
215+
visitors.exit(node)
216+
236217
/** @type {Array<Property | SpreadElement>} */
237218
const defaults = []
238219
/** @type {Array<string>} */
@@ -242,22 +223,17 @@ export function recmaJsxRewrite(options) {
242223
/** @type {Array<VariableDeclarator>} */
243224
const declarations = []
244225

245-
if (currentScope && currentScope.node === node) {
246-
// Cast to patch our `node`.
247-
currentScope = /** @type {Scope} */ (currentScope.parent)
248-
}
249-
250226
if (
251227
node.type === 'FunctionDeclaration' ||
252228
node.type === 'FunctionExpression' ||
253229
node.type === 'ArrowFunctionExpression'
254230
) {
255-
const scopeNode = node
256-
const scope = functionStack[functionStack.length - 1]
231+
const functionInfo = functionStack[functionStack.length - 1]
232+
257233
/** @type {string} */
258234
let name
259235

260-
for (name of scope.tags.sort()) {
236+
for (name of functionInfo.tags.sort()) {
261237
defaults.push({
262238
type: 'Property',
263239
kind: 'init',
@@ -271,9 +247,9 @@ export function recmaJsxRewrite(options) {
271247
})
272248
}
273249

274-
actual.push(...scope.components)
250+
actual.push(...functionInfo.components)
275251

276-
for (name of scope.objects) {
252+
for (name of functionInfo.objects) {
277253
// In some cases, a component is used directly (`<X>`) but it’s also
278254
// used as an object (`<X.Y>`).
279255
if (!actual.includes(name)) {
@@ -289,7 +265,7 @@ export function recmaJsxRewrite(options) {
289265
if (
290266
defaults.length > 0 ||
291267
actual.length > 0 ||
292-
scope.idToInvalidComponentName.size > 0
268+
functionInfo.idToInvalidComponentName.size > 0
293269
) {
294270
if (providerImportSource) {
295271
importProvider = true
@@ -304,8 +280,8 @@ export function recmaJsxRewrite(options) {
304280
// Accept `components` as a prop if this is the `MDXContent` or
305281
// `_createMdxContent` function.
306282
if (
307-
isNamedFunction(scope.node, 'MDXContent') ||
308-
isNamedFunction(scope.node, '_createMdxContent')
283+
isNamedFunction(functionInfo.node, 'MDXContent') ||
284+
isNamedFunction(functionInfo.node, '_createMdxContent')
309285
) {
310286
parameters.push(toIdOrMemberExpression(['props', 'components']))
311287
}
@@ -360,7 +336,7 @@ export function recmaJsxRewrite(options) {
360336
}
361337
}
362338

363-
if (scope.tags.length > 0) {
339+
if (functionInfo.tags.length > 0) {
364340
declarations.push({
365341
type: 'VariableDeclarator',
366342
id: {type: 'Identifier', name: '_components'},
@@ -369,9 +345,9 @@ export function recmaJsxRewrite(options) {
369345
componentsInit = {type: 'Identifier', name: '_components'}
370346
}
371347

372-
if (isNamedFunction(scope.node, '_createMdxContent')) {
348+
if (isNamedFunction(functionInfo.node, '_createMdxContent')) {
373349
for (const [id, componentName] of [
374-
...scope.idToInvalidComponentName
350+
...functionInfo.idToInvalidComponentName
375351
].sort(function ([a], [b]) {
376352
return a.localeCompare(b)
377353
})) {
@@ -418,27 +394,28 @@ export function recmaJsxRewrite(options) {
418394
let key
419395

420396
// Add partials (so for `x.y.z` it’d generate `x` and `x.y` too).
421-
for (key in scope.references) {
422-
if (Object.hasOwn(scope.references, key)) {
397+
for (key in functionInfo.references) {
398+
if (Object.hasOwn(functionInfo.references, key)) {
423399
const parts = key.split('.')
424400
let index = 0
425401
while (++index < parts.length) {
426402
const partial = parts.slice(0, index).join('.')
427-
if (!Object.hasOwn(scope.references, partial)) {
428-
scope.references[partial] = {
403+
if (!Object.hasOwn(functionInfo.references, partial)) {
404+
functionInfo.references[partial] = {
429405
component: false,
430-
node: scope.references[key].node
406+
node: functionInfo.references[key].node
431407
}
432408
}
433409
}
434410
}
435411
}
436412

437-
const references = Object.keys(scope.references).sort()
413+
const references = Object.keys(functionInfo.references).sort()
414+
438415
let index = -1
439416
while (++index < references.length) {
440417
const id = references[index]
441-
const info = scope.references[id]
418+
const info = functionInfo.references[id]
442419
const place = stringifyPosition(positionFromEstree(info.node))
443420
/** @type {Array<Expression>} */
444421
const parameters = [
@@ -475,14 +452,14 @@ export function recmaJsxRewrite(options) {
475452

476453
if (statements.length > 0) {
477454
// Arrow functions with an implied return:
478-
if (scopeNode.body.type !== 'BlockStatement') {
479-
scopeNode.body = {
455+
if (node.body.type !== 'BlockStatement') {
456+
node.body = {
480457
type: 'BlockStatement',
481-
body: [{type: 'ReturnStatement', argument: scopeNode.body}]
458+
body: [{type: 'ReturnStatement', argument: node.body}]
482459
}
483460
}
484461

485-
scopeNode.body.body.unshift(...statements)
462+
node.body.body.unshift(...statements)
486463
}
487464

488465
functionStack.pop()
@@ -620,26 +597,22 @@ function isNamedFunction(node, name) {
620597
}
621598

622599
/**
623-
* @param {Readonly<Scope>} scope
600+
* @param {Array<Scope>} scopes
624601
* Scope.
625602
* @param {string} id
626603
* Identifier.
627604
* @returns {boolean}
628605
* Whether `id` is in `scope`.
629606
*/
630-
function inScope(scope, id) {
631-
/** @type {Scope | undefined} */
632-
let currentScope = scope
607+
function inScope(scopes, id) {
608+
let index = scopes.length
633609

634-
while (currentScope) {
635-
if (currentScope.declarations.has(id)) {
610+
while (index--) {
611+
const scope = scopes[index]
612+
613+
if (scope.defined.includes(id)) {
636614
return true
637615
}
638-
639-
// Cast to patch our `node`.
640-
currentScope = /** @type {Scope | undefined} */ (
641-
currentScope.parent || undefined
642-
)
643616
}
644617

645618
return false

Diff for: ‎packages/mdx/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@
5151
"devlop": "^1.0.0",
5252
"estree-util-build-jsx": "^3.0.0",
5353
"estree-util-is-identifier-name": "^3.0.0",
54+
"estree-util-scope": "^1.0.0",
5455
"estree-util-to-js": "^2.0.0",
5556
"estree-walker": "^3.0.0",
5657
"hast-util-to-estree": "^3.0.0",
5758
"hast-util-to-jsx-runtime": "^2.0.0",
5859
"markdown-extensions": "^2.0.0",
59-
"periscopic": "^3.0.0",
6060
"remark-mdx": "^3.0.0",
6161
"remark-parse": "^11.0.0",
6262
"remark-rehype": "^11.0.0",

Diff for: ‎renovate.json5

-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
11
{
22
extends: ['config:base', ':preserveSemverRanges'],
3-
packageRules: [
4-
{
5-
enabled: false,
6-
matchPackageNames: [
7-
// `periscopic@4` does not work at all.
8-
'periscopic'
9-
]
10-
}
11-
],
123
schedule: 'before 3am on Monday'
134
}

Diff for: ‎website/mdx-config.js

+20-3
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import {common} from '@wooorm/starry-night'
2121
import sourceMdx from '@wooorm/starry-night/source.mdx'
2222
import sourceToml from '@wooorm/starry-night/source.toml'
2323
import sourceTsx from '@wooorm/starry-night/source.tsx'
24+
import {createVisitors} from 'estree-util-scope'
2425
import {valueToEstree} from 'estree-util-value-to-estree'
26+
import {walk} from 'estree-walker'
2527
import {h, s} from 'hastscript'
2628
import {toText} from 'hast-util-to-text'
27-
import {analyze} from 'periscopic'
2829
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
2930
import rehypeInferDescriptionMeta from 'rehype-infer-description-meta'
3031
import rehypeInferReadingTimeMeta from 'rehype-infer-reading-time-meta'
@@ -196,10 +197,26 @@ function recmaInjectMeta(options) {
196197
*/
197198
return function (tree, file) {
198199
// Find everything that’s defined in the top-level scope.
199-
const topScope = analyze(tree).scope.declarations
200+
const visitors = createVisitors()
201+
walk(tree, {
202+
enter(node) {
203+
visitors.enter(node)
204+
205+
if (
206+
node.type === 'ArrowFunctionExpression' ||
207+
node.type === 'FunctionDeclaration' ||
208+
node.type === 'FunctionExpression'
209+
) {
210+
this.skip()
211+
visitors.exit(node) // Call the exit handler manually.
212+
}
213+
},
214+
leave: visitors.exit
215+
})
216+
const topScope = visitors.scopes[0]
200217

201218
// Exit if `meta` is already defined.
202-
if (topScope.has('meta')) return
219+
if (topScope.defined.includes('meta')) return
203220

204221
// Treat as arbitrary object.
205222
const meta = /** @type {Record<string, unknown>} */ (file.data.meta || {})

0 commit comments

Comments
 (0)
Please sign in to comment.