namespace-object-resolution.test.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. /**
  2. * The default-export namespace object — `const UploadApi = { uploadARCapture };
  3. * export default UploadApi` — and a call through it from another file. Two
  4. * things have to hold for `handleZipComplete → uploadARCapture` to exist:
  5. * the default import must find the constant the `export default` statement
  6. * names (it is not exported at its declaration), and the member must resolve
  7. * to the binding the shorthand property carries, through the object's own
  8. * imports.
  9. */
  10. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  11. import * as fs from 'node:fs';
  12. import * as path from 'node:path';
  13. import * as os from 'node:os';
  14. import { CodeGraph } from '../src';
  15. describe('namespace object default exports', () => {
  16. let dir: string;
  17. beforeEach(() => {
  18. dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-namespace-object-'));
  19. });
  20. afterEach(() => {
  21. fs.rmSync(dir, { recursive: true, force: true });
  22. });
  23. function write(rel: string, content: string): void {
  24. const full = path.join(dir, rel);
  25. fs.mkdirSync(path.dirname(full), { recursive: true });
  26. fs.writeFileSync(full, content);
  27. }
  28. it('resolves Api.member() to the function the shorthand property names', async () => {
  29. write('package.json', '{"name":"app"}');
  30. write('src/api/frames.ts', 'export async function uploadARCapture(uri: string) {\n return uri\n}\n');
  31. write('src/api/folders.ts', 'export function createFolder(name: string) {\n return name\n}\n');
  32. write(
  33. 'src/api/index.ts',
  34. "import { uploadARCapture } from './frames'\n" +
  35. "import { createFolder } from './folders'\n" +
  36. 'function localHelper() {\n return 1\n}\n' +
  37. 'const UploadApi = {\n uploadARCapture,\n makeFolder: createFolder,\n localHelper,\n}\n' +
  38. 'export default UploadApi\n'
  39. );
  40. write(
  41. 'src/hooks.ts',
  42. "import UploadApi from './api'\n" +
  43. 'export function handleZipComplete(uri: string) {\n' +
  44. ' UploadApi.makeFolder(uri)\n' +
  45. ' UploadApi.localHelper()\n' +
  46. ' return UploadApi.uploadARCapture(uri)\n' +
  47. '}\n'
  48. );
  49. const cg = await CodeGraph.init(dir, { silent: true });
  50. await cg.indexAll();
  51. const handler = cg.getNodesByName('handleZipComplete')[0]!;
  52. const callees = cg.getCallees(handler.id).map((c) => c.node.name).sort();
  53. cg.close();
  54. expect(callees).toEqual(['createFolder', 'localHelper', 'uploadARCapture']);
  55. });
  56. it('a default import of a later-exported const finds that const, and a method inside it', async () => {
  57. write('package.json', '{"name":"app"}');
  58. write(
  59. 'src/store.ts',
  60. 'const useStore = {\n read() {\n return 1\n },\n}\nexport function unrelated() {\n return 2\n}\nexport default useStore\n'
  61. );
  62. write('src/use.ts', "import store from './store'\nexport function consume() {\n return store.read()\n}\n");
  63. const cg = await CodeGraph.init(dir, { silent: true });
  64. await cg.indexAll();
  65. const consume = cg.getNodesByName('consume')[0]!;
  66. const callees = cg.getCallees(consume.id).map((c) => c.node.name);
  67. cg.close();
  68. // Without the `export default NAME` binding the default import guessed the
  69. // first exported function (`unrelated`); now it is the object, and the
  70. // member resolves inside it.
  71. expect(callees).toEqual(['read']);
  72. });
  73. });