mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-16 20:53:57 +08:00
5f5b8db620
TreeSitterExtractor.isExportedLater and its kernel mirror is_exported_later
bounded the name in the `export { ... }` branch with `\b`, which takes a `$`
for a separator. It never fires beside a leading or trailing `$`, so
`const items$ = create(...)` + `export { items$ };` (or `export { $items as
default };` for a `$items` store) never matched, and the store's actions
were not extracted on either engine. It also matched inside a longer name:
`export { useStore$ }` or `export { $useStore }` counted as exporting a
store named `useStore`.
The clause branch now bounds the name with a character that can't continue
an identifier. The regex crate has no look-around, so the bounds consume,
and both sides spell the class in ASCII, since the crate's `\w` is Unicode
while JS's is ASCII. Plain names decide as before apart from the two false
positives. A non-ASCII letter written against the name (`export {
éuseStore }`) used to split the engines (the kernel's Unicode `\b` said not
exported, the wasm path exported); both now give the wasm answer.
Adds kernel/wasm parity cases (ts/tsx/js/jsx) inside 'a store exported by a
later statement', and the same cases in store-exported-later.test.ts, for
`items$`, `$items as default` and both false-positive shapes. They fail with
the unfixed kernel and with the unfixed TS on the wasm path, and pass with
both fixes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
/**
|
|
* A store exported by a LATER statement — `const useStore = create(…)` then
|
|
* `export default useStore` — is exported, and its actions are extracted like
|
|
* an `export const` store's (object-literal-methods.test.ts covers that
|
|
* form). The scope rule that keeps inline-object noise out still holds: a
|
|
* store nothing exports stays a constant.
|
|
*/
|
|
import { describe, it, expect, beforeAll } from 'vitest';
|
|
import { extractFromSource } from '../src/extraction';
|
|
import { initGrammars, loadAllGrammars } from '../src/extraction/grammars';
|
|
|
|
beforeAll(async () => {
|
|
await initGrammars();
|
|
await loadAllGrammars();
|
|
});
|
|
|
|
const fnNames = (code: string, file = 'store.ts') =>
|
|
extractFromSource(file, code)
|
|
.nodes.filter((n) => n.kind === 'function')
|
|
.map((n) => n.name);
|
|
|
|
describe('store actions on a later-exported const', () => {
|
|
it('export default NAME', () => {
|
|
const code = `
|
|
import { create } from 'zustand'
|
|
const useCaptureStorage = create<State>((set, get) => ({
|
|
object: null,
|
|
setSettings: (settings: Settings) => {
|
|
set({ settings })
|
|
},
|
|
reset: () => set({ object: null }),
|
|
}))
|
|
export default useCaptureStorage
|
|
`;
|
|
expect(fnNames(code)).toEqual(expect.arrayContaining(['setSettings', 'reset']));
|
|
});
|
|
|
|
it('export { NAME } and export { NAME as default }', () => {
|
|
const named = `
|
|
const useStore = create((set) => ({ bump: () => set({}) }))
|
|
export { useStore }
|
|
`;
|
|
const asDefault = `
|
|
const useStore = create((set) => ({ bump: () => set({}) }))
|
|
export { useStore as default }
|
|
`;
|
|
expect(fnNames(named)).toContain('bump');
|
|
expect(fnNames(asDefault)).toContain('bump');
|
|
});
|
|
|
|
it.each([
|
|
['items$', 'export { items$ };', true],
|
|
['$items', 'export { $items as default };', true],
|
|
['items', 'export { items$ };', false],
|
|
['items', 'export { $items };', false],
|
|
] as const)('an export clause names %s only whole: `%s`', (name, exportLine, kept) => {
|
|
// A `$` belongs to the name: `items$` is exported, `items` is not.
|
|
const code = `
|
|
const ${name} = create((set) => ({ inc: () => set({}) }))
|
|
${exportLine}
|
|
`;
|
|
expect(fnNames(code).includes('inc')).toBe(kept);
|
|
});
|
|
|
|
it('a const nothing exports keeps its members out of the graph', () => {
|
|
const code = `
|
|
const useStore = create((set) => ({ bump: () => set({}) }))
|
|
export const other = 1
|
|
`;
|
|
expect(fnNames(code)).not.toContain('bump');
|
|
});
|
|
|
|
it('is not fooled by a different name in the export', () => {
|
|
const code = `
|
|
const useStoreInternal = create((set) => ({ bump: () => set({}) }))
|
|
const useStore = 1
|
|
export default useStore
|
|
`;
|
|
expect(fnNames(code)).not.toContain('bump');
|
|
});
|
|
});
|