@crouton-kit/tsym 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/bin/tsym.js +2 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +89 -0
  6. package/dist/commands/handlers/check.d.ts +3 -0
  7. package/dist/commands/handlers/check.js +94 -0
  8. package/dist/commands/handlers/find.d.ts +3 -0
  9. package/dist/commands/handlers/find.js +56 -0
  10. package/dist/commands/handlers/inspect.d.ts +3 -0
  11. package/dist/commands/handlers/inspect.js +81 -0
  12. package/dist/commands/handlers/outline.d.ts +3 -0
  13. package/dist/commands/handlers/outline.js +101 -0
  14. package/dist/commands/handlers/query.d.ts +3 -0
  15. package/dist/commands/handlers/query.js +196 -0
  16. package/dist/commands/handlers/rename.d.ts +3 -0
  17. package/dist/commands/handlers/rename.js +53 -0
  18. package/dist/commands/handlers/search.d.ts +5 -0
  19. package/dist/commands/handlers/search.js +32 -0
  20. package/dist/commands/handlers/server-reload.d.ts +3 -0
  21. package/dist/commands/handlers/server-reload.js +7 -0
  22. package/dist/commands/handlers/server-status.d.ts +3 -0
  23. package/dist/commands/handlers/server-status.js +7 -0
  24. package/dist/commands/handlers/server-stop.d.ts +3 -0
  25. package/dist/commands/handlers/server-stop.js +7 -0
  26. package/dist/commands/handlers/show.d.ts +3 -0
  27. package/dist/commands/handlers/show.js +63 -0
  28. package/dist/commands/handlers/type.d.ts +3 -0
  29. package/dist/commands/handlers/type.js +45 -0
  30. package/dist/commands/handlers.d.ts +12 -0
  31. package/dist/commands/handlers.js +44 -0
  32. package/dist/commands/help.d.ts +2 -0
  33. package/dist/commands/help.js +27 -0
  34. package/dist/commands/rename-apply.d.ts +37 -0
  35. package/dist/commands/rename-apply.js +115 -0
  36. package/dist/commands/tree.d.ts +9 -0
  37. package/dist/commands/tree.js +277 -0
  38. package/dist/commands/types.d.ts +80 -0
  39. package/dist/commands/types.js +1 -0
  40. package/dist/core/declarations.d.ts +48 -0
  41. package/dist/core/declarations.js +153 -0
  42. package/dist/core/receipt.d.ts +7 -0
  43. package/dist/core/receipt.js +9 -0
  44. package/dist/core/search.d.ts +29 -0
  45. package/dist/core/search.js +47 -0
  46. package/dist/core/source.d.ts +32 -0
  47. package/dist/core/source.js +96 -0
  48. package/dist/core/workspace.d.ts +28 -0
  49. package/dist/core/workspace.js +137 -0
  50. package/dist/index/build.d.ts +22 -0
  51. package/dist/index/build.js +79 -0
  52. package/dist/index/classify.d.ts +20 -0
  53. package/dist/index/classify.js +60 -0
  54. package/dist/index/commits.d.ts +15 -0
  55. package/dist/index/commits.js +201 -0
  56. package/dist/index/descriptor.d.ts +19 -0
  57. package/dist/index/descriptor.js +295 -0
  58. package/dist/index/graph.d.ts +82 -0
  59. package/dist/index/graph.js +1 -0
  60. package/dist/index/heritage.d.ts +15 -0
  61. package/dist/index/heritage.js +122 -0
  62. package/dist/index/refresh.d.ts +28 -0
  63. package/dist/index/refresh.js +110 -0
  64. package/dist/index/scope.d.ts +4 -0
  65. package/dist/index/scope.js +21 -0
  66. package/dist/index/state.d.ts +22 -0
  67. package/dist/index/state.js +63 -0
  68. package/dist/index/walk.d.ts +23 -0
  69. package/dist/index/walk.js +550 -0
  70. package/dist/index/watch.d.ts +18 -0
  71. package/dist/index/watch.js +175 -0
  72. package/dist/index.d.ts +4 -0
  73. package/dist/index.js +2 -0
  74. package/dist/output.d.ts +12 -0
  75. package/dist/output.js +54 -0
  76. package/dist/read/read.d.ts +32 -0
  77. package/dist/read/read.js +89 -0
  78. package/dist/relations/graph.d.ts +23 -0
  79. package/dist/relations/graph.js +115 -0
  80. package/dist/relations/mermaid.d.ts +3 -0
  81. package/dist/relations/mermaid.js +37 -0
  82. package/dist/relations/store-relations.d.ts +20 -0
  83. package/dist/relations/store-relations.js +158 -0
  84. package/dist/relations/types.d.ts +58 -0
  85. package/dist/relations/types.js +12 -0
  86. package/dist/server/client.d.ts +5 -0
  87. package/dist/server/client.js +215 -0
  88. package/dist/server/context.d.ts +21 -0
  89. package/dist/server/context.js +12 -0
  90. package/dist/server/lifecycle.d.ts +28 -0
  91. package/dist/server/lifecycle.js +62 -0
  92. package/dist/server/main.d.ts +1 -0
  93. package/dist/server/main.js +541 -0
  94. package/dist/server/projects.d.ts +21 -0
  95. package/dist/server/projects.js +28 -0
  96. package/dist/server/wire.d.ts +39 -0
  97. package/dist/server/wire.js +19 -0
  98. package/dist/store/load.d.ts +67 -0
  99. package/dist/store/load.js +193 -0
  100. package/dist/store/meta.d.ts +28 -0
  101. package/dist/store/meta.js +137 -0
  102. package/dist/store/query.d.ts +5 -0
  103. package/dist/store/query.js +4 -0
  104. package/dist/store/schema.d.ts +4 -0
  105. package/dist/store/schema.js +19 -0
  106. package/dist/store/store.d.ts +36 -0
  107. package/dist/store/store.js +240 -0
  108. package/dist/ts7/api.d.ts +35 -0
  109. package/dist/ts7/api.js +164 -0
  110. package/dist/ts7/ast.d.ts +1 -0
  111. package/dist/ts7/ast.js +1 -0
  112. package/dist/ts7/checker.d.ts +55 -0
  113. package/dist/ts7/checker.js +383 -0
  114. package/dist/ts7/projects.d.ts +27 -0
  115. package/dist/ts7/projects.js +94 -0
  116. package/package.json +47 -0
@@ -0,0 +1,20 @@
1
+ import type { Symbol as TsSymbol } from 'typescript/unstable/sync';
2
+ import type { KeyedDeclaration } from './descriptor.js';
3
+ import type { ExternalNode } from './graph.js';
4
+ export type Target = {
5
+ key: string;
6
+ external: false;
7
+ } | {
8
+ key: string;
9
+ external: true;
10
+ node: ExternalNode;
11
+ } | null;
12
+ export interface DeclarationLookup {
13
+ root: string;
14
+ indexedByStart: ReadonlyMap<string, ReadonlyMap<number, KeyedDeclaration>>;
15
+ modulesByFile: ReadonlyMap<string, string>;
16
+ }
17
+ export declare function declarationTarget(symbol: TsSymbol, lookup: DeclarationLookup, resolveAlias: (symbol: TsSymbol) => TsSymbol, externalIdentity?: string): Target;
18
+ export declare function moduleTarget(symbol: TsSymbol | undefined, lookup: DeclarationLookup, resolveAlias: (symbol: TsSymbol) => TsSymbol, specifier: string): Target;
19
+ export declare function relative(root: string, file: string): string;
20
+ export declare function packageName(specifier: string): string;
@@ -0,0 +1,60 @@
1
+ import path from 'node:path';
2
+ export function declarationTarget(symbol, lookup, resolveAlias, externalIdentity) {
3
+ const resolved = resolveAlias(symbol);
4
+ const declarations = resolved.declarations;
5
+ for (const handle of declarations) {
6
+ const declaration = handle.resolve();
7
+ if (!declaration)
8
+ continue;
9
+ const file = relative(lookup.root, declaration.getSourceFile().fileName);
10
+ const descriptor = lookup.indexedByStart.get(file)?.get(declaration.getStart(declaration.getSourceFile()));
11
+ if (descriptor)
12
+ return { key: descriptor.key, external: false };
13
+ }
14
+ if (declarations.some((handle) => {
15
+ const declaration = handle.resolve();
16
+ return declaration && lookup.modulesByFile.has(relative(lookup.root, declaration.getSourceFile().fileName));
17
+ }))
18
+ return null;
19
+ const declaration = declarations.map((handle) => handle.resolve()).find((node) => !!node);
20
+ const module = externalModule(declaration?.getSourceFile().fileName, externalIdentity ?? resolved.name);
21
+ const name = resolved.name;
22
+ const node = { key: `${module}#${name}`, module, name };
23
+ return { key: node.key, external: true, node };
24
+ }
25
+ export function moduleTarget(symbol, lookup, resolveAlias, specifier) {
26
+ if (symbol) {
27
+ const declarations = resolveAlias(symbol).declarations;
28
+ for (const handle of declarations) {
29
+ const declaration = handle.resolve();
30
+ if (!declaration)
31
+ continue;
32
+ const key = lookup.modulesByFile.get(relative(lookup.root, declaration.getSourceFile().fileName));
33
+ if (key)
34
+ return { key, external: false };
35
+ }
36
+ }
37
+ const module = packageName(specifier);
38
+ const node = { key: `${module}#<module>`, module, name: '<module>' };
39
+ return { key: node.key, external: true, node };
40
+ }
41
+ export function relative(root, file) {
42
+ return path.relative(root, file).split(path.sep).join('/');
43
+ }
44
+ export function packageName(specifier) {
45
+ const segments = specifier.split('/');
46
+ if (specifier.startsWith('@'))
47
+ return segments[1]?.endsWith('.js') ? segments[0] : segments.slice(0, 2).join('/');
48
+ return segments[0] || specifier;
49
+ }
50
+ function externalModule(file, fallback) {
51
+ if (!file)
52
+ return fallback;
53
+ const normalized = file.split(path.sep).join('/');
54
+ if (/\/lib\.[^.]+\.d\.ts$/.test(normalized))
55
+ return 'lib';
56
+ const nodeModules = normalized.lastIndexOf('/node_modules/');
57
+ if (nodeModules >= 0)
58
+ return packageName(normalized.slice(nodeModules + '/node_modules/'.length));
59
+ return fallback;
60
+ }
@@ -0,0 +1,15 @@
1
+ import type { RelationshipRow } from '../store/load.js';
2
+ import { Store } from '../store/store.js';
3
+ export interface RenameCandidate {
4
+ from: string;
5
+ oldKey: string;
6
+ basis: 'text-identical' | 'container-renamed' | 'file-moved';
7
+ commit: string;
8
+ }
9
+ export interface CommitIngestion {
10
+ commits: readonly string[];
11
+ changed: readonly RelationshipRow[];
12
+ renamed: readonly RenameCandidate[];
13
+ }
14
+ /** Ingests missing commits against working-tree identities. Committed blobs are parsed only by the TS5 descriptor. */
15
+ export declare function ingestCommits(root: string, store: Store, hashes: readonly string[]): CommitIngestion;
@@ -0,0 +1,201 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { describeSourceFile, parseDescriptorSource } from './descriptor.js';
3
+ /** Ingests missing commits against working-tree identities. Committed blobs are parsed only by the TS5 descriptor. */
4
+ export function ingestCommits(root, store, hashes) {
5
+ const current = currentDeclarations(store);
6
+ const commits = [];
7
+ const changed = [];
8
+ const renamed = [];
9
+ for (const hash of hashes) {
10
+ if (commitExists(store, hash))
11
+ continue;
12
+ const ingestion = ingestCommit(root, hash, current);
13
+ store.transaction(() => {
14
+ store.execute('CREATE (:Commit {hash: $hash, ts: $ts, author: $author, subject: $subject})', commitDetails(root, hash));
15
+ for (const relationship of ingestion.changed) {
16
+ store.execute('MATCH (c:Commit), (d:Declaration) WHERE c.hash = $commit AND d.key = $key CREATE (c)-[:CHANGED {added: $added, removed: $removed}]->(d)', {
17
+ commit: hash,
18
+ key: relationship.to,
19
+ added: relationship.added ?? 0,
20
+ removed: relationship.removed ?? 0,
21
+ });
22
+ }
23
+ for (const candidate of ingestion.renamed) {
24
+ store.execute('MATCH (d:Declaration), (c:Commit) WHERE d.key = $from AND c.hash = $commit CREATE (d)-[:RENAMED_FROM {oldKey: $oldKey, basis: $basis}]->(c)', {
25
+ from: candidate.from,
26
+ commit: candidate.commit,
27
+ oldKey: candidate.oldKey,
28
+ basis: candidate.basis,
29
+ });
30
+ }
31
+ });
32
+ commits.push(hash);
33
+ changed.push(...ingestion.changed);
34
+ renamed.push(...ingestion.renamed);
35
+ }
36
+ return { commits, changed, renamed };
37
+ }
38
+ function ingestCommit(root, hash, current) {
39
+ const parent = parentOf(root, hash);
40
+ const changed = new Map();
41
+ const beforeForRename = [];
42
+ const afterForRename = [];
43
+ for (const file of touchedFiles(root, parent, hash)) {
44
+ if (!isSourceFile(file))
45
+ continue;
46
+ const committed = descriptorsAt(root, hash, file);
47
+ const after = cleanAtCommit(root, hash, file) ? currentSpans(current, file) : committed;
48
+ const before = parent === undefined ? [] : descriptorsAt(root, parent, file);
49
+ beforeForRename.push(...before);
50
+ afterForRename.push(...committed);
51
+ const afterIndexed = after.filter((declaration) => current.has(declaration.key));
52
+ const beforeByKey = new Map(before.map((declaration) => [declaration.key, declaration]));
53
+ for (const hunk of hunksFor(root, parent, hash, file)) {
54
+ if (hunk.newLines === 0) {
55
+ const target = deletionTarget(afterIndexed, hunk.newStart);
56
+ if (target)
57
+ add(changed, target.key, 0, hunk.oldLines);
58
+ continue;
59
+ }
60
+ const affected = afterIndexed.filter((declaration) => declaration.name !== '<module>' && intersects(declaration.startLine, declaration.endLine, hunk.newStart, hunk.newStart + hunk.newLines - 1));
61
+ const targets = affected.length === 0 ? (moduleFor(afterIndexed) === undefined ? [] : [moduleFor(afterIndexed)]) : affected;
62
+ for (const target of targets) {
63
+ const previous = beforeByKey.get(target.key);
64
+ const removed = previous && hunk.oldLines > 0
65
+ ? intersection(previous.startLine, previous.endLine, hunk.oldStart, hunk.oldStart + hunk.oldLines - 1)
66
+ : 0;
67
+ add(changed, target.key, intersection(target.startLine, target.endLine, hunk.newStart, hunk.newStart + hunk.newLines - 1), removed);
68
+ }
69
+ }
70
+ }
71
+ return {
72
+ changed: [...changed].map(([to, counts]) => ({ type: 'CHANGED', from: hash, to, target: 'Declaration', ...counts })),
73
+ renamed: renameCandidates(hash, beforeForRename, afterForRename, current),
74
+ };
75
+ }
76
+ function currentDeclarations(store) {
77
+ const rows = store.execute('MATCH (d:Declaration) RETURN d.key AS key, d.file AS file, d.endLine AS endLine, d.lines AS lines');
78
+ return new Map(rows.map((row) => [String(row.key), { key: String(row.key), file: String(row.file), endLine: Number(row.endLine), lines: Number(row.lines) }]));
79
+ }
80
+ function commitExists(store, hash) {
81
+ return store.execute('MATCH (c:Commit) WHERE c.hash = $hash RETURN c.hash', { hash }).length > 0;
82
+ }
83
+ function commitDetails(root, hash) {
84
+ const values = git(root, ['show', '-s', '--format=%aI%x00%an%x00%s', hash]).split('\0');
85
+ if (values.length !== 3)
86
+ throw new Error(`Git did not return commit metadata for ${hash}.`);
87
+ return { hash, ts: values[0], author: values[1], subject: values[2].trimEnd() };
88
+ }
89
+ function parentOf(root, hash) {
90
+ return git(root, ['rev-list', '--parents', '-n', '1', hash]).trim().split(/\s+/)[1];
91
+ }
92
+ function touchedFiles(root, parent, hash) {
93
+ const args = parent === undefined
94
+ ? ['diff-tree', '--root', '--no-commit-id', '--name-only', '-r', hash]
95
+ : ['diff', '--no-renames', '--name-only', parent, hash];
96
+ return git(root, args).split('\n').filter(Boolean);
97
+ }
98
+ function hunksFor(root, parent, hash, file) {
99
+ const args = parent === undefined
100
+ ? ['diff-tree', '--root', '--no-commit-id', '--unified=0', '-r', hash, '--', file]
101
+ : ['diff', '--unified=0', parent, hash, '--', file];
102
+ return git(root, args).split('\n').flatMap((line) => {
103
+ const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
104
+ return match ? [{ oldStart: Number(match[1]), oldLines: Number(match[2] ?? 1), newStart: Number(match[3]), newLines: Number(match[4] ?? 1) }] : [];
105
+ });
106
+ }
107
+ function descriptorsAt(root, commit, file) {
108
+ let text;
109
+ try {
110
+ text = git(root, ['show', `${commit}:${file}`]);
111
+ }
112
+ catch (error) {
113
+ if (/does not exist in|exists on disk, but not in/.test(String(error)))
114
+ return [];
115
+ throw error;
116
+ }
117
+ const source = parseDescriptorSource(file, text);
118
+ return describeSourceFile(source, file).map((declaration) => ({
119
+ ...declaration,
120
+ startLine: source.getLineAndCharacterOfPosition(declaration.start).line + 1,
121
+ endLine: source.getLineAndCharacterOfPosition(Math.max(declaration.start, declaration.end - 1)).line + 1,
122
+ text: text.slice(declaration.start, declaration.end).replace(/\s+/g, ' ').trim(),
123
+ }));
124
+ }
125
+ function currentSpans(current, file) {
126
+ return [...current.values()].filter((declaration) => declaration.file === file).map((declaration) => ({
127
+ key: declaration.key,
128
+ name: declaration.key.endsWith('#<module>') ? '<module>' : declaration.key.slice(declaration.key.lastIndexOf('.') + 1),
129
+ startLine: declaration.endLine - declaration.lines + 1,
130
+ endLine: declaration.endLine,
131
+ }));
132
+ }
133
+ function cleanAtCommit(root, hash, file) {
134
+ try {
135
+ execFileSync('git', ['diff', '--quiet', hash, '--', file], { cwd: root });
136
+ return true;
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ function deletionTarget(declarations, line) {
143
+ const nonModules = declarations.filter((declaration) => declaration.name !== '<module>');
144
+ const preceding = nonModules.filter((declaration) => declaration.endLine === line - 1);
145
+ if (preceding.length > 0)
146
+ return innermost(preceding);
147
+ const containing = nonModules.filter((declaration) => declaration.startLine <= line && line <= declaration.endLine);
148
+ return containing.length > 0 ? innermost(containing) : moduleFor(declarations);
149
+ }
150
+ function moduleFor(declarations) {
151
+ return declarations.find((declaration) => declaration.name === '<module>');
152
+ }
153
+ function innermost(declarations) {
154
+ return [...declarations].sort((left, right) => (left.endLine - left.startLine) - (right.endLine - right.startLine) || left.key.localeCompare(right.key))[0];
155
+ }
156
+ function renameCandidates(commit, before, after, current) {
157
+ const oldByText = new Map();
158
+ for (const declaration of before) {
159
+ if (declaration.name === '<module>' || current.has(declaration.key))
160
+ continue;
161
+ const candidates = oldByText.get(declaration.text) ?? [];
162
+ candidates.push(declaration);
163
+ oldByText.set(declaration.text, candidates);
164
+ }
165
+ const candidates = [];
166
+ for (const declaration of after) {
167
+ if (declaration.name === '<module>' || !current.has(declaration.key))
168
+ continue;
169
+ const matches = oldByText.get(declaration.text) ?? [];
170
+ if (matches.length !== 1)
171
+ continue;
172
+ const old = matches[0];
173
+ if (old.key === declaration.key)
174
+ continue;
175
+ candidates.push({
176
+ from: declaration.key,
177
+ oldKey: old.key,
178
+ basis: old.file !== declaration.file ? 'file-moved' : old.containerPath !== declaration.containerPath ? 'container-renamed' : 'text-identical',
179
+ commit,
180
+ });
181
+ }
182
+ return candidates;
183
+ }
184
+ function add(target, key, added, removed) {
185
+ const previous = target.get(key) ?? { added: 0, removed: 0 };
186
+ previous.added += added;
187
+ previous.removed += removed;
188
+ target.set(key, previous);
189
+ }
190
+ function intersects(start, end, otherStart, otherEnd) {
191
+ return start <= otherEnd && otherStart <= end;
192
+ }
193
+ function intersection(start, end, otherStart, otherEnd) {
194
+ return Math.max(0, Math.min(end, otherEnd) - Math.max(start, otherStart) + 1);
195
+ }
196
+ function isSourceFile(file) {
197
+ return /\.[cm]?[jt]sx?$/.test(file);
198
+ }
199
+ function git(root, args) {
200
+ return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
201
+ }
@@ -0,0 +1,19 @@
1
+ import * as ts from 'ts5';
2
+ export type DescriptorScope = 'module' | 'top-level' | 'named-member' | 'function-local' | 'anonymous-expression' | 'excluded' | 'other';
3
+ export interface KeyedDeclaration {
4
+ key: string;
5
+ file: string;
6
+ name: string;
7
+ path: string;
8
+ containerPath: string;
9
+ kind: string;
10
+ start: number;
11
+ end: number;
12
+ nameStart: number;
13
+ nameEnd: number;
14
+ scope: DescriptorScope;
15
+ ownsCallSite: boolean;
16
+ }
17
+ export declare function parseDescriptorSource(file: string, text: string): ts.SourceFile;
18
+ export declare function describeFile(file: string, text: string): KeyedDeclaration[];
19
+ export declare function describeSourceFile(source: ts.SourceFile, file?: string): KeyedDeclaration[];
@@ -0,0 +1,295 @@
1
+ import * as ts from 'ts5';
2
+ const ANON = '<anon:';
3
+ const COMPUTED = '<computed>';
4
+ export function parseDescriptorSource(file, text) {
5
+ return ts.createSourceFile(file, text, ts.ScriptTarget.ES2022, true, scriptKind(file));
6
+ }
7
+ export function describeFile(file, text) {
8
+ return describeSourceFile(parseDescriptorSource(file, text), file);
9
+ }
10
+ export function describeSourceFile(source, file = source.fileName) {
11
+ const descriptors = [{
12
+ key: `${file}#<module>`,
13
+ file,
14
+ name: '<module>',
15
+ path: '<module>',
16
+ containerPath: '',
17
+ kind: 'module',
18
+ start: 0,
19
+ end: source.end,
20
+ nameStart: 0,
21
+ nameEnd: 0,
22
+ scope: 'module',
23
+ ownsCallSite: false,
24
+ }];
25
+ const anonymousCounts = new Map();
26
+ const roleCounts = new Map();
27
+ const anchorCounts = new Map();
28
+ const literalRoleCounts = new Map();
29
+ const nextRole = (container, role) => {
30
+ const key = `${container.join('.')}|${role}`;
31
+ const count = (roleCounts.get(key) ?? 0) + 1;
32
+ roleCounts.set(key, count);
33
+ return count;
34
+ };
35
+ const anchorFor = (node, container) => {
36
+ const parent = node.parent;
37
+ if (!parent || !ts.isCallExpression(parent))
38
+ return undefined;
39
+ const argument = parent.arguments.indexOf(node);
40
+ const callee = callName(parent.expression);
41
+ if (argument < 0 || !callee)
42
+ return undefined;
43
+ const first = parent.arguments[0];
44
+ const base = first && first !== node && ts.isStringLiteralLike(first)
45
+ ? `<${callee}(${JSON.stringify(first.text)})>`
46
+ : `<${callee}#arg${argument + 1}:${nextRole(container, `${callee}${argument}`)}>`;
47
+ const key = `${container.join('.')}|${base}`;
48
+ const count = (anchorCounts.get(key) ?? 0) + 1;
49
+ anchorCounts.set(key, count);
50
+ return count === 1 ? base : `${base.slice(0, -1)}#${count}>`;
51
+ };
52
+ const anonymousName = (node, container) => {
53
+ const anchor = anchorFor(node, container);
54
+ if (anchor)
55
+ return anchor;
56
+ const key = container.join('.');
57
+ const count = (anonymousCounts.get(key) ?? 0) + 1;
58
+ anonymousCounts.set(key, count);
59
+ return `${ANON}${count}>`;
60
+ };
61
+ const nextLiteralRole = (path, label, role) => {
62
+ const key = `${path.join('.')}|${label}|${role}`;
63
+ const count = (literalRoleCounts.get(key) ?? 0) + 1;
64
+ literalRoleCounts.set(key, count);
65
+ return count;
66
+ };
67
+ const literalRole = (node, path) => {
68
+ const parent = node.parent;
69
+ const label = ts.isArrayLiteralExpression(node) ? 'arr' : ts.isObjectLiteralExpression(node) ? 'obj' : 'type';
70
+ if (parent && ts.isReturnStatement(parent))
71
+ return `<${label}:return:${nextLiteralRole(path, label, 'return')}>`;
72
+ if (parent && ts.isArrayLiteralExpression(parent))
73
+ return `<${label}:elem${parent.elements.indexOf(node)}>`;
74
+ return `<${label}:${nextLiteralRole(path, label, 'literal')}>`;
75
+ };
76
+ const blockSegment = (node, path) => {
77
+ const parent = node.parent;
78
+ if (ts.isBlock(node)) {
79
+ if (parent && ts.isIfStatement(parent))
80
+ return parent.thenStatement === node ? `<then:${nextRole(path, 'then')}>` : `<else:${nextRole(path, 'else')}>`;
81
+ if (parent && ts.isTryStatement(parent) && parent.tryBlock === node)
82
+ return `<try:${nextRole(path, 'try')}>`;
83
+ if (parent && ts.isTryStatement(parent) && parent.finallyBlock === node)
84
+ return `<finally:${nextRole(path, 'finally')}>`;
85
+ if (parent && (ts.isFunctionLike(parent) || ts.isModuleDeclaration(parent) || ts.isCatchClause(parent) || ts.isIterationStatement(parent, false) || ts.isLabeledStatement(parent)))
86
+ return undefined;
87
+ return `<block:${nextRole(path, 'block')}>`;
88
+ }
89
+ if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node))
90
+ return `<loop:${nextRole(path, 'loop')}>`;
91
+ if (ts.isCatchClause(node))
92
+ return `<catch:${nextRole(path, 'catch')}>`;
93
+ if (ts.isCaseClause(node) || ts.isDefaultClause(node))
94
+ return `<case:${nextRole(path, 'case')}>`;
95
+ return undefined;
96
+ };
97
+ const record = (node, segment, path, scope) => {
98
+ const declarationPath = [...path, segment];
99
+ const name = declarationNameNode(node) ?? node;
100
+ descriptors.push({
101
+ key: `${file}#${declarationPath.join('.')}`,
102
+ file,
103
+ name: segment,
104
+ path: declarationPath.join('.'),
105
+ containerPath: path.join('.'),
106
+ kind: ts.SyntaxKind[node.kind],
107
+ start: node.getStart(source),
108
+ end: node.getEnd(),
109
+ nameStart: name.getStart(source),
110
+ nameEnd: name.getEnd(),
111
+ scope,
112
+ ownsCallSite: ownsCallSite(node),
113
+ });
114
+ return declarationPath;
115
+ };
116
+ const visit = (node, path, parentNamed, nameless) => {
117
+ let childPath = path;
118
+ let pushed = false;
119
+ let scope = 'other';
120
+ const push = (segment, nextScope, containerPath = path) => {
121
+ childPath = record(node, segment, containerPath, nextScope);
122
+ scope = nextScope;
123
+ pushed = true;
124
+ };
125
+ if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
126
+ push(node.name?.text ?? (isDefaultExport(node) ? 'default' : anonymousName(node, path)), topOrFunctionLocal(node));
127
+ }
128
+ else if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
129
+ push(node.name.getText(source), topOrNamedMember(node));
130
+ }
131
+ else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
132
+ push(node.name.text, ts.isCatchClause(node.parent) ? 'excluded' : topOrFunctionLocal(node));
133
+ }
134
+ else if (ts.isBindingElement(node) && ts.isIdentifier(node.name)) {
135
+ push(node.name.text, bindingElementScope(node));
136
+ }
137
+ else if (ts.isParameter(node) && ts.isIdentifier(node.name)) {
138
+ if (ts.isParameterPropertyDeclaration(node, node.parent))
139
+ push(node.name.text, 'named-member', path.slice(0, -1));
140
+ else
141
+ push(node.name.text, 'excluded');
142
+ }
143
+ else if (ts.isTypeParameterDeclaration(node)) {
144
+ push(node.name.text, 'excluded');
145
+ }
146
+ else if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertyDeclaration(node) || ts.isPropertySignature(node) || ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isEnumMember(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) {
147
+ const name = propertyName(node.name, source) ?? anonymousName(node, path);
148
+ const isExcluded = ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || (ts.isPropertySignature(node) && inlineTypeLiteralMember(node));
149
+ push(name, isExcluded ? 'excluded' : topOrNamedMember(node));
150
+ }
151
+ else if (ts.isConstructorDeclaration(node)) {
152
+ push('constructor', topOrNamedMember(node));
153
+ }
154
+ else if (ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isClassExpression(node)) {
155
+ if (!borrowsParentName(node))
156
+ push(anonymousName(node, path), 'anonymous-expression');
157
+ }
158
+ else if (ts.isExportAssignment(node) && isAnonymousExport(node.expression)) {
159
+ push('default', 'top-level');
160
+ }
161
+ if (!pushed && isNamelessLiteral(node, parentNamed, nameless)) {
162
+ const parent = node.parent;
163
+ const arm = parent && (ts.isUnionTypeNode(parent) || ts.isIntersectionTypeNode(parent)) ? `<arm:${parent.types.indexOf(node) + 1}>` : undefined;
164
+ childPath = [...path, arm ?? anchorFor(node, path) ?? literalRole(node, path)];
165
+ }
166
+ if (parentNamed && node.parent && isTypeAnnotation(node.parent, node))
167
+ childPath = [...childPath, '<type>'];
168
+ const block = blockSegment(node, childPath);
169
+ if (block)
170
+ childPath = [...childPath, block];
171
+ ts.forEachChild(node, (child) => visit(child, childPath, pushed, nameless || (!parentNamed && (ts.isObjectLiteralExpression(node) || ts.isTypeLiteralNode(node) || ts.isMappedTypeNode(node)))));
172
+ };
173
+ ts.forEachChild(source, (node) => visit(node, [], false, false));
174
+ return descriptors;
175
+ }
176
+ function scriptKind(file) {
177
+ if (file.endsWith('.tsx'))
178
+ return ts.ScriptKind.TSX;
179
+ if (file.endsWith('.jsx'))
180
+ return ts.ScriptKind.JSX;
181
+ if (file.endsWith('.js') || file.endsWith('.mjs') || file.endsWith('.cjs'))
182
+ return ts.ScriptKind.JS;
183
+ return ts.ScriptKind.TS;
184
+ }
185
+ function declarationNameNode(node) {
186
+ const name = node.name;
187
+ return name && typeof name !== 'string' ? name : undefined;
188
+ }
189
+ function propertyName(name, source) {
190
+ if (!name)
191
+ return undefined;
192
+ if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name) || ts.isNumericLiteral(name))
193
+ return name.text;
194
+ if (ts.isComputedPropertyName(name))
195
+ return `[computed:${name.expression.getText(source).replace(/\s+/g, ' ').trim()}]`;
196
+ return undefined;
197
+ }
198
+ function isDefaultExport(node) {
199
+ const modifiers = ts.getModifiers(node);
200
+ return !!modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) && !!modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
201
+ }
202
+ function callName(expression) {
203
+ const text = expression.getText().replace(/\s+/g, ' ').trim();
204
+ return text || undefined;
205
+ }
206
+ function borrowsParentName(node) {
207
+ const parent = node.parent;
208
+ if (ts.isVariableDeclaration(parent) && parent.initializer === node && ts.isIdentifier(parent.name))
209
+ return true;
210
+ if (ts.isPropertyAssignment(parent) && parent.initializer === node)
211
+ return true;
212
+ if (ts.isPropertyDeclaration(parent) && parent.initializer === node)
213
+ return true;
214
+ if (ts.isBindingElement(parent) && parent.initializer === node)
215
+ return true;
216
+ if (ts.isParameter(parent) && parent.initializer === node)
217
+ return true;
218
+ if (ts.isEnumMember(parent) && parent.initializer === node)
219
+ return true;
220
+ if (ts.isExportAssignment(parent))
221
+ return true;
222
+ if (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isSatisfiesExpression(parent) || ts.isTypeAssertionExpression(parent))
223
+ return borrowsParentName(parent);
224
+ return false;
225
+ }
226
+ function isAnonymousExport(node) {
227
+ return ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isClassExpression(node) || ts.isObjectLiteralExpression(node);
228
+ }
229
+ function isNamelessLiteral(node, parentNamed, alreadyNameless) {
230
+ if (parentNamed || alreadyNameless)
231
+ return false;
232
+ return ts.isObjectLiteralExpression(node) || ts.isTypeLiteralNode(node) || ts.isMappedTypeNode(node) || ts.isArrayLiteralExpression(node);
233
+ }
234
+ function isTypeAnnotation(parent, node) {
235
+ return 'type' in parent && parent.type === node && !ts.isTypeAliasDeclaration(parent) && ts.isTypeNode(node);
236
+ }
237
+ function bindingElementScope(node) {
238
+ for (let current = node.parent; current; current = current.parent) {
239
+ if (ts.isVariableDeclaration(current))
240
+ return ts.isCatchClause(current.parent) ? 'excluded' : topOrFunctionLocal(current);
241
+ if (ts.isParameter(current) || ts.isCatchClause(current))
242
+ return 'excluded';
243
+ }
244
+ return 'excluded';
245
+ }
246
+ function topOrFunctionLocal(node) {
247
+ if (isTopLevel(node))
248
+ return 'top-level';
249
+ if (namedMemberContainer(node))
250
+ return 'named-member';
251
+ return hasFunctionAncestor(node) ? 'function-local' : 'other';
252
+ }
253
+ function topOrNamedMember(node) {
254
+ if (isTopLevel(node))
255
+ return 'top-level';
256
+ return namedMemberContainer(node) ? 'named-member' : 'other';
257
+ }
258
+ function isTopLevel(node) {
259
+ let current = node.parent;
260
+ while (current && !ts.isSourceFile(current)) {
261
+ if (ts.isFunctionLike(current) || ts.isClassLike(current) || ts.isInterfaceDeclaration(current) || ts.isEnumDeclaration(current) || ts.isModuleDeclaration(current) || ts.isTypeAliasDeclaration(current) || ts.isModuleBlock(current) || ts.isTypeLiteralNode(current) || ts.isObjectLiteralExpression(current) || ts.isBlock(current))
262
+ return false;
263
+ current = current.parent;
264
+ }
265
+ return !!current;
266
+ }
267
+ function hasFunctionAncestor(node) {
268
+ for (let current = node.parent; current && !ts.isSourceFile(current); current = current.parent)
269
+ if (ts.isFunctionLike(current))
270
+ return true;
271
+ return false;
272
+ }
273
+ function namedMemberContainer(node) {
274
+ for (let current = node.parent; current && !ts.isSourceFile(current); current = current.parent) {
275
+ if (ts.isClassDeclaration(current) || ts.isInterfaceDeclaration(current) || ts.isEnumDeclaration(current) || ts.isModuleDeclaration(current) || ts.isTypeAliasDeclaration(current))
276
+ return true;
277
+ if (ts.isFunctionLike(current) || ts.isObjectLiteralExpression(current) || ts.isClassStaticBlockDeclaration(current) || ts.isBlock(current) || ts.isCaseBlock(current) || ts.isCaseClause(current) || ts.isDefaultClause(current) || ts.isCatchClause(current) || ts.isIterationStatement(current, false))
278
+ return false;
279
+ }
280
+ return false;
281
+ }
282
+ function inlineTypeLiteralMember(node) {
283
+ return ts.isTypeLiteralNode(node.parent) && !ts.isTypeAliasDeclaration(node.parent.parent);
284
+ }
285
+ function ownsCallSite(node) {
286
+ let found = false;
287
+ const visit = (child) => {
288
+ if (ts.isCallExpression(child))
289
+ found = true;
290
+ else
291
+ ts.forEachChild(child, visit);
292
+ };
293
+ ts.forEachChild(node, visit);
294
+ return found;
295
+ }