do not rewrite assignments
This commit is contained in:
@@ -78,6 +78,34 @@ describe('transform', () => {
|
||||
expect(out).not.toContain('import *')
|
||||
})
|
||||
|
||||
test('bails when the chain is a write target', () => {
|
||||
const writes = [
|
||||
'app.bsky.feed.like = 1',
|
||||
'app.bsky.feed.like++',
|
||||
'delete app.bsky.feed.like',
|
||||
'for (app.bsky.feed.like of []) {}',
|
||||
';[app.bsky.feed.like] = []',
|
||||
';({x: app.bsky.feed.like} = {})',
|
||||
]
|
||||
for (const stmt of writes) {
|
||||
const out = applyPlugin(
|
||||
`import {app} from './lexicons'\n${stmt}\n`,
|
||||
PROBE_FILE,
|
||||
)
|
||||
expect(out).toContain(`from './lexicons'`)
|
||||
expect(out).not.toContain('import *')
|
||||
}
|
||||
})
|
||||
|
||||
test('rewrites a read of a leaf even when a sibling member is written', () => {
|
||||
const out = applyPlugin(
|
||||
`import {app} from './lexicons'\ndelete app.bsky.feed.like.$cached\n`,
|
||||
PROBE_FILE,
|
||||
)
|
||||
expect(out).toContain('delete _lex_app_bsky_feed_like.$cached')
|
||||
expect(out).not.toContain(`from './lexicons'`)
|
||||
})
|
||||
|
||||
test('leaves type-only imports untouched', () => {
|
||||
const src = `import type {app} from './lexicons'\nexport type T = typeof app\n`
|
||||
const out = applyPlugin(src, PROBE_FILE)
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
* which imports its own copy of the barrel via '../lexicons/index.js'.
|
||||
*
|
||||
* Correctness fallback: if any reference to a barrel binding cannot be rewritten
|
||||
* (namespace used as a value, computed access, chain ending at a non-leaf), that
|
||||
* binding is left on the barrel import. The result is always correct, merely
|
||||
* (namespace used as a value, computed access, chain ending at a non-leaf,
|
||||
* chain in a write position), that binding is left on the barrel import. The result is always correct, merely
|
||||
* unshaken for that file. Set BSKY_LEXICON_IMPORTS_DEBUG=1 to log such bails.
|
||||
*
|
||||
* Caveat: the rewrite bakes leaf file paths into each consumer's transform
|
||||
@@ -96,6 +96,28 @@ function leafFileFor(dir, segment) {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the member chain is written to rather than read. Such a chain
|
||||
* cannot be collapsed into a bare identifier: imports are read-only bindings
|
||||
* (assignment/++ would throw where the original property write may not) and
|
||||
* `delete <identifier>` is a strict-mode SyntaxError in the emitted code.
|
||||
*/
|
||||
function isWriteTarget(memberPath) {
|
||||
const parent = memberPath.parentPath
|
||||
if (!parent) return false
|
||||
if (parent.isAssignmentExpression()) {
|
||||
return parent.node.left === memberPath.node
|
||||
}
|
||||
if (parent.isUpdateExpression()) return true
|
||||
if (parent.isUnaryExpression({operator: 'delete'})) return true
|
||||
if (parent.isForXStatement()) return parent.node.left === memberPath.node
|
||||
if (parent.isArrayPattern() || parent.isRestElement()) return true
|
||||
if (parent.isObjectProperty() && parent.parentPath.isObjectPattern()) {
|
||||
return parent.node.value === memberPath.node
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** `rootDir\0segments\0leafFile` -> boolean */
|
||||
const chainCache = new Map()
|
||||
|
||||
@@ -208,6 +230,7 @@ module.exports = function lexiconLeafImports(babel, options = {}) {
|
||||
for (;;) {
|
||||
const kind = classify(dir, segment)
|
||||
if (kind === 'file') {
|
||||
if (isWriteTarget(cur)) return null
|
||||
return {memberPath: cur, leafFile: leafFileFor(dir, segment), segments}
|
||||
}
|
||||
if (kind !== 'dir') return null
|
||||
|
||||
Reference in New Issue
Block a user