headlesscode 1.0.2
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.
- package/ATTRIBUTION.md +53 -0
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +107 -0
- package/LICENSE +202 -0
- package/README.md +486 -0
- package/SECURITY.md +211 -0
- package/bin/headlesscode.mjs +83 -0
- package/package.json +63 -0
- package/shared/prompts/review-mode-prompt-short.md +93 -0
- package/shared/prompts/review-mode-prompt.md +281 -0
- package/shared/rules-code/rules.md +22 -0
- package/shared/stacks/cpp/rules.md +30 -0
- package/shared/stacks/fastapi/rules.md +30 -0
- package/shared/stacks/javascript/rules.md +37 -0
- package/shared/stacks/postgresql/rules.md +31 -0
- package/shared/stacks/python/rules.md +35 -0
- package/shared/stacks/react/rules.md +11 -0
- package/shared/stacks/typescript/rules.md +10 -0
- package/src/budget/budget.ts +221 -0
- package/src/budget/concurrency.ts +126 -0
- package/src/budget/cost.ts +309 -0
- package/src/budget/index.ts +8 -0
- package/src/checkpoints/cli.ts +256 -0
- package/src/checkpoints/service.ts +227 -0
- package/src/cli.ts +1535 -0
- package/src/cloud/docker-provider.ts +334 -0
- package/src/cloud/provider.ts +300 -0
- package/src/codeintel/call-graph.ts +78 -0
- package/src/codeintel/find-references.ts +123 -0
- package/src/codeintel/go-to-definition.ts +193 -0
- package/src/codeintel/handlers.ts +190 -0
- package/src/codeintel/import-graph.ts +173 -0
- package/src/codeintel/outline.ts +180 -0
- package/src/codeintel/position.ts +77 -0
- package/src/codeintel/program.ts +350 -0
- package/src/codeintel/rename-symbol.ts +213 -0
- package/src/codeintel/tools.ts +280 -0
- package/src/codemap/build.ts +135 -0
- package/src/codemap/cli.ts +190 -0
- package/src/codemap/extract.ts +339 -0
- package/src/codemap/files.ts +236 -0
- package/src/codemap/fingerprint.ts +65 -0
- package/src/codemap/flows.ts +62 -0
- package/src/codemap/html.ts +451 -0
- package/src/codemap/lock.ts +80 -0
- package/src/codemap/types.ts +101 -0
- package/src/codesearch/airunner-embedder.ts +185 -0
- package/src/codesearch/chunk.ts +339 -0
- package/src/codesearch/cli.ts +223 -0
- package/src/codesearch/embedder.ts +332 -0
- package/src/codesearch/files.ts +280 -0
- package/src/codesearch/index.ts +469 -0
- package/src/codesearch/ollama-embedder.ts +205 -0
- package/src/codesearch/search.ts +141 -0
- package/src/codesearch/types.ts +100 -0
- package/src/config/mode-models.ts +218 -0
- package/src/dashboard/aggregate.ts +364 -0
- package/src/dashboard/chat-thread.ts +141 -0
- package/src/dashboard/checkpoints.ts +124 -0
- package/src/dashboard/cli.ts +193 -0
- package/src/dashboard/codemap.ts +44 -0
- package/src/dashboard/files.ts +121 -0
- package/src/dashboard/page.ts +2803 -0
- package/src/dashboard/self-improvement-metrics.ts +282 -0
- package/src/dashboard/server.ts +1103 -0
- package/src/dashboard/session-launch.ts +310 -0
- package/src/dashboard/timeline.ts +273 -0
- package/src/dashboard/tool-exec.ts +107 -0
- package/src/dashboard/trend-cli.ts +141 -0
- package/src/dashboard/trend.ts +413 -0
- package/src/decision-proxy/cli.ts +261 -0
- package/src/decision-proxy/proxy.ts +569 -0
- package/src/deploy/gate-cli.ts +147 -0
- package/src/deploy/gate.ts +254 -0
- package/src/engine/condense.ts +512 -0
- package/src/engine/events.ts +428 -0
- package/src/engine/handoff.ts +71 -0
- package/src/engine/lazy-tools.ts +160 -0
- package/src/engine/local-explore.ts +653 -0
- package/src/engine/logger.ts +96 -0
- package/src/engine/loop.ts +5517 -0
- package/src/engine/parser.ts +347 -0
- package/src/engine/prompt.ts +860 -0
- package/src/engine/reports.ts +47 -0
- package/src/engine/stacks.ts +448 -0
- package/src/engine/types.ts +291 -0
- package/src/engine/usage.ts +186 -0
- package/src/github/app-auth.ts +161 -0
- package/src/github/cli.ts +448 -0
- package/src/github/installations.ts +133 -0
- package/src/github/pr.ts +321 -0
- package/src/github/provision.ts +118 -0
- package/src/github/push.ts +122 -0
- package/src/index-util.ts +50 -0
- package/src/index.ts +81 -0
- package/src/init/cli.ts +248 -0
- package/src/init/gitignore.ts +74 -0
- package/src/llm/ollama.ts +308 -0
- package/src/llm/openrouter.ts +868 -0
- package/src/llm/preflight.ts +367 -0
- package/src/llm/transcript-capture.ts +84 -0
- package/src/memory/embed.ts +110 -0
- package/src/memory/index.ts +22 -0
- package/src/memory/local.ts +259 -0
- package/src/memory/summarizer.ts +283 -0
- package/src/memory/types.ts +153 -0
- package/src/memory/uwuchat.ts +157 -0
- package/src/migrate/cli.ts +115 -0
- package/src/orchestrator/analyze-cli.ts +104 -0
- package/src/orchestrator/auto-split.ts +206 -0
- package/src/orchestrator/cleanup.ts +1003 -0
- package/src/orchestrator/cli.ts +3571 -0
- package/src/orchestrator/cost-estimate.ts +564 -0
- package/src/orchestrator/cost-history-cli.ts +242 -0
- package/src/orchestrator/cost-history.ts +397 -0
- package/src/orchestrator/git-sync.ts +250 -0
- package/src/orchestrator/index.ts +153 -0
- package/src/orchestrator/log-analysis.ts +0 -0
- package/src/orchestrator/merge-check.ts +108 -0
- package/src/orchestrator/pipeline.ts +411 -0
- package/src/orchestrator/resume.ts +1940 -0
- package/src/orchestrator/reviewer.ts +503 -0
- package/src/orchestrator/split.ts +296 -0
- package/src/orchestrator/state.ts +542 -0
- package/src/orchestrator/status.ts +697 -0
- package/src/orchestrator/verification-gate.ts +134 -0
- package/src/orchestrator/watch.ts +898 -0
- package/src/permissions/commands.ts +1083 -0
- package/src/permissions/config.ts +241 -0
- package/src/permissions/index.ts +12 -0
- package/src/permissions/protected-files.ts +96 -0
- package/src/permissions/store-protection.ts +272 -0
- package/src/project-store.ts +648 -0
- package/src/projects/cli.ts +382 -0
- package/src/qa/qa.ts +487 -0
- package/src/tools/browser/handler.ts +346 -0
- package/src/tools/browser/service.ts +406 -0
- package/src/tools/browser/smoke.ts +78 -0
- package/src/tools/browser/tool.ts +99 -0
- package/src/tools/executor.ts +2575 -0
- package/src/tools/language-detect.ts +183 -0
- package/src/tools/output-summarizer.ts +369 -0
- package/src/tools/run-tests.ts +302 -0
- package/src/tools/set-indentation-tool.ts +49 -0
- package/src/tools/test-selection.ts +160 -0
- package/src/vendor/tests/smoke.ts +103 -0
- package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
- package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
- package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
- package/src/vendor/zoo-code/shim/os-name.ts +18 -0
- package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
- package/src/vendor/zoo-code/shim/vscode.ts +76 -0
- package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
- package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
- package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
- package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
- package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
- package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
- package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
- package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
- package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
- package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
- package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
- package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
- package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
- package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
- package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
- package/src/vendor/zoo-code/src/shared/language.ts +43 -0
- package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
- package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
- package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
- package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
- package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
- package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
- package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
- package/src/vendor/zoo-code/src/utils/object.ts +18 -0
- package/src/vendor/zoo-code/src/utils/path.ts +94 -0
- package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
- package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
- package/src/vendor/zoo-code/types/global-settings.ts +19 -0
- package/src/vendor/zoo-code/types/index.ts +22 -0
- package/src/vendor/zoo-code/types/message.ts +375 -0
- package/src/vendor/zoo-code/types/mode.ts +241 -0
- package/src/vendor/zoo-code/types/todo.ts +19 -0
- package/src/vendor/zoo-code/types/tool-params.ts +116 -0
- package/src/vendor/zoo-code/types/tool.ts +67 -0
- package/src/vendor/zoo-code/types/vscode.ts +84 -0
- package/src/vision/describe.ts +242 -0
- package/src/vision/tool.ts +91 -0
- package/src/watcher/cli.ts +369 -0
- package/src/watcher/github.ts +304 -0
- package/src/watcher/index.ts +59 -0
- package/src/watcher/state.ts +254 -0
- package/src/watcher/watch.ts +562 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `import_graph` analysis: (a) what a file imports, resolved to real file
|
|
3
|
+
* paths (not raw specifiers), and (b) what imports it (reverse lookup).
|
|
4
|
+
*
|
|
5
|
+
* Static analysis of `ts.ImportDeclaration` / `ts.ExportDeclaration` /
|
|
6
|
+
* `ts.ImportEqualsDeclaration` nodes — imports are syntactic, not
|
|
7
|
+
* type-dependent, so this needs no type-checking: specifiers are resolved
|
|
8
|
+
* with `ts.resolveModuleName` against the cached program's compiler options
|
|
9
|
+
* (so `paths` aliases like `vscode` → the shim resolve exactly as the
|
|
10
|
+
* compiler would).
|
|
11
|
+
*
|
|
12
|
+
* The whole-program import map is built ONCE per loaded program and cached
|
|
13
|
+
* alongside it (see CodeIntelCache): reverse lookups never rescan the
|
|
14
|
+
* project per call.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as path from "node:path"
|
|
18
|
+
import * as ts from "typescript"
|
|
19
|
+
|
|
20
|
+
import type { CodeIntel } from "./program.js"
|
|
21
|
+
|
|
22
|
+
export interface ImportEdge {
|
|
23
|
+
/** Raw module specifier as written in source (e.g. "./foo" or "vscode"). */
|
|
24
|
+
specifier: string
|
|
25
|
+
/** Real resolved file path (undefined when resolution fails). */
|
|
26
|
+
resolved: string | undefined
|
|
27
|
+
/** True for `export ... from` re-exports. */
|
|
28
|
+
isReExport: boolean
|
|
29
|
+
/** True for `import type` / `export type`. */
|
|
30
|
+
isTypeOnly: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ImportGraph {
|
|
34
|
+
/** Per absolute file path: everything it imports. */
|
|
35
|
+
imports: Map<string, ImportEdge[]>
|
|
36
|
+
/** Per resolved file path: every absolute file path that imports it. */
|
|
37
|
+
importedBy: Map<string, string[]>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Resolve a module specifier the same way the compiler would. */
|
|
41
|
+
function resolveSpecifier(
|
|
42
|
+
intel: CodeIntel,
|
|
43
|
+
containingFile: string,
|
|
44
|
+
specifier: string,
|
|
45
|
+
): string | undefined {
|
|
46
|
+
const result = ts.resolveModuleName(specifier, containingFile, intel.compilerOptions, {
|
|
47
|
+
fileExists: ts.sys.fileExists,
|
|
48
|
+
readFile: ts.sys.readFile,
|
|
49
|
+
getCurrentDirectory: () => path.dirname(containingFile),
|
|
50
|
+
directoryExists: ts.sys.directoryExists,
|
|
51
|
+
})
|
|
52
|
+
return result.resolvedModule?.resolvedFileName
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build (or return the cached) import graph for the program. The graph is a
|
|
57
|
+
* pure function of the program's source files, so it is computed once and
|
|
58
|
+
* reused until the program itself is rebuilt.
|
|
59
|
+
*/
|
|
60
|
+
export function getImportGraph(intel: CodeIntel): ImportGraph {
|
|
61
|
+
const cached = (intel as { __importGraph?: ImportGraph }).__importGraph
|
|
62
|
+
if (cached !== undefined) {
|
|
63
|
+
return cached
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const imports = new Map<string, ImportEdge[]>()
|
|
67
|
+
const importedBy = new Map<string, string[]>()
|
|
68
|
+
|
|
69
|
+
const addReverseEdge = (resolved: string | undefined, importer: string): void => {
|
|
70
|
+
if (resolved === undefined) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
const list = importedBy.get(resolved) ?? []
|
|
74
|
+
if (!list.includes(importer)) {
|
|
75
|
+
list.push(importer)
|
|
76
|
+
importedBy.set(resolved, list)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const sourceFile of intel.program.getSourceFiles()) {
|
|
81
|
+
const fileImports: ImportEdge[] = []
|
|
82
|
+
|
|
83
|
+
const collect = (node: ts.Node): void => {
|
|
84
|
+
if (ts.isImportDeclaration(node)) {
|
|
85
|
+
if (ts.isStringLiteral(node.moduleSpecifier)) {
|
|
86
|
+
const specifier = node.moduleSpecifier.text
|
|
87
|
+
const resolved = resolveSpecifier(intel, sourceFile.fileName, specifier)
|
|
88
|
+
fileImports.push({
|
|
89
|
+
specifier,
|
|
90
|
+
resolved,
|
|
91
|
+
isReExport: false,
|
|
92
|
+
// The flag lives on the import clause, not the declaration.
|
|
93
|
+
isTypeOnly: node.importClause?.isTypeOnly === true,
|
|
94
|
+
})
|
|
95
|
+
addReverseEdge(resolved, sourceFile.fileName)
|
|
96
|
+
}
|
|
97
|
+
} else if (ts.isExportDeclaration(node)) {
|
|
98
|
+
// ExportDeclaration.moduleSpecifier is optional (`export {}`).
|
|
99
|
+
if (node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
100
|
+
const specifier = node.moduleSpecifier.text
|
|
101
|
+
const resolved = resolveSpecifier(intel, sourceFile.fileName, specifier)
|
|
102
|
+
fileImports.push({
|
|
103
|
+
specifier,
|
|
104
|
+
resolved,
|
|
105
|
+
isReExport: true,
|
|
106
|
+
isTypeOnly: node.isTypeOnly === true,
|
|
107
|
+
})
|
|
108
|
+
addReverseEdge(resolved, sourceFile.fileName)
|
|
109
|
+
}
|
|
110
|
+
} else if (ts.isImportEqualsDeclaration(node)) {
|
|
111
|
+
const ref = node.moduleReference
|
|
112
|
+
if (ts.isExternalModuleReference(ref) && ref.expression !== undefined && ts.isStringLiteral(ref.expression)) {
|
|
113
|
+
const specifier = ref.expression.text
|
|
114
|
+
const resolved = resolveSpecifier(intel, sourceFile.fileName, specifier)
|
|
115
|
+
fileImports.push({
|
|
116
|
+
specifier,
|
|
117
|
+
resolved,
|
|
118
|
+
isReExport: false,
|
|
119
|
+
isTypeOnly: false,
|
|
120
|
+
})
|
|
121
|
+
addReverseEdge(resolved, sourceFile.fileName)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ts.forEachChild(sourceFile, (node) => collect(node))
|
|
127
|
+
if (fileImports.length > 0) {
|
|
128
|
+
imports.set(sourceFile.fileName, fileImports)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const graph: ImportGraph = { imports, importedBy }
|
|
133
|
+
;(intel as { __importGraph?: ImportGraph }).__importGraph = graph
|
|
134
|
+
return graph
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Absolute path → workspace-relative (posix) for display; absolute if outside. */
|
|
138
|
+
function displayPath(workspaceRoot: string, p: string): string {
|
|
139
|
+
const rel = path.relative(workspaceRoot, p)
|
|
140
|
+
return rel === "" || rel.startsWith("..") ? p : rel.split(path.sep).join("/")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Render the import graph for one file. */
|
|
144
|
+
export function formatImportGraph(
|
|
145
|
+
workspaceRoot: string,
|
|
146
|
+
fileName: string,
|
|
147
|
+
graph: ImportGraph,
|
|
148
|
+
): string {
|
|
149
|
+
const out: string[] = []
|
|
150
|
+
|
|
151
|
+
const edges = graph.imports.get(fileName) ?? []
|
|
152
|
+
out.push(`imports (${edges.length}):`)
|
|
153
|
+
if (edges.length === 0) {
|
|
154
|
+
out.push(" (none)")
|
|
155
|
+
}
|
|
156
|
+
for (const edge of edges) {
|
|
157
|
+
const target = edge.resolved !== undefined ? displayPath(workspaceRoot, edge.resolved) : `(unresolved: ${edge.specifier})`
|
|
158
|
+
const typeOnly = edge.isTypeOnly ? "type " : ""
|
|
159
|
+
const reExport = edge.isReExport ? "export " : "import "
|
|
160
|
+
out.push(` ${reExport}${typeOnly}${edge.specifier} -> ${target}`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const importers = graph.importedBy.get(fileName) ?? []
|
|
164
|
+
out.push(`imported by (${importers.length}):`)
|
|
165
|
+
if (importers.length === 0) {
|
|
166
|
+
out.push(" (none)")
|
|
167
|
+
}
|
|
168
|
+
for (const importer of importers.sort()) {
|
|
169
|
+
out.push(` ${displayPath(workspaceRoot, importer)}`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return out.join("\n")
|
|
173
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `outline` analysis: every top-level (and one level of nested) declaration
|
|
3
|
+
* in a source file, as `line kind signature` — one compact line per symbol,
|
|
4
|
+
* signatures only, no bodies. Built on the real AST (`ts.forEachChild`), not
|
|
5
|
+
* regex: kinds come from the node types, lines from
|
|
6
|
+
* `ts.getLineAndCharacterOfPosition`, and signatures are the node's own
|
|
7
|
+
* source text cut at its body — so `14 function foo(x: string): void` is
|
|
8
|
+
* exactly what the source says, no guesswork.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as ts from "typescript"
|
|
12
|
+
|
|
13
|
+
export interface OutlineEntry {
|
|
14
|
+
/** 1-based line of the declaration's name. */
|
|
15
|
+
line: number
|
|
16
|
+
kind: string
|
|
17
|
+
signature: string
|
|
18
|
+
/** Nesting depth: 0 = top level, 1 = class/interface/namespace member. */
|
|
19
|
+
depth: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Map a TS node to a short human kind label. */
|
|
23
|
+
function kindOf(node: ts.Node): string | undefined {
|
|
24
|
+
if (ts.isFunctionDeclaration(node)) return "function"
|
|
25
|
+
if (ts.isClassDeclaration(node)) return "class"
|
|
26
|
+
if (ts.isInterfaceDeclaration(node)) return "interface"
|
|
27
|
+
if (ts.isTypeAliasDeclaration(node)) return "type"
|
|
28
|
+
if (ts.isEnumDeclaration(node)) return "enum"
|
|
29
|
+
if (ts.isModuleDeclaration(node)) return "namespace"
|
|
30
|
+
if (ts.isVariableStatement(node)) return "var"
|
|
31
|
+
if (ts.isMethodDeclaration(node)) return "method"
|
|
32
|
+
if (ts.isPropertyDeclaration(node)) return "property"
|
|
33
|
+
if (ts.isGetAccessorDeclaration(node)) return "getter"
|
|
34
|
+
if (ts.isSetAccessorDeclaration(node)) return "setter"
|
|
35
|
+
if (ts.isPropertySignature(node)) return "property"
|
|
36
|
+
if (ts.isMethodSignature(node)) return "method"
|
|
37
|
+
if (ts.isCallSignatureDeclaration(node)) return "call-signature"
|
|
38
|
+
if (ts.isConstructSignatureDeclaration(node)) return "construct-signature"
|
|
39
|
+
if (ts.isIndexSignatureDeclaration(node)) return "index-signature"
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** True for function-like nodes whose body is a block we must cut off. */
|
|
44
|
+
function hasBlockBody(node: ts.Node): boolean {
|
|
45
|
+
return (
|
|
46
|
+
ts.isFunctionDeclaration(node) ||
|
|
47
|
+
ts.isMethodDeclaration(node) ||
|
|
48
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
49
|
+
ts.isSetAccessorDeclaration(node)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A container whose members are worth outlining one level deep. */
|
|
54
|
+
function isContainer(node: ts.Node): boolean {
|
|
55
|
+
return (
|
|
56
|
+
ts.isClassDeclaration(node) ||
|
|
57
|
+
ts.isInterfaceDeclaration(node) ||
|
|
58
|
+
ts.isModuleDeclaration(node) ||
|
|
59
|
+
ts.isEnumDeclaration(node)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The declaration's signature text WITHOUT its body: for functions, source
|
|
65
|
+
* from the node start up to the body's `{`; for types/classes/interfaces,
|
|
66
|
+
* up to the first `{` or `;`. Internal whitespace is collapsed to one space
|
|
67
|
+
* so multi-line signatures stay on a single output line.
|
|
68
|
+
*/
|
|
69
|
+
function signatureOf(node: ts.Node, sourceFile: ts.SourceFile): string {
|
|
70
|
+
const full = node.getText(sourceFile)
|
|
71
|
+
let cut = full
|
|
72
|
+
if (hasBlockBody(node)) {
|
|
73
|
+
const body = (node as ts.FunctionLikeDeclaration).body
|
|
74
|
+
if (body !== undefined) {
|
|
75
|
+
cut = full.slice(0, body.getStart(sourceFile) - node.getStart(sourceFile))
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
const brace = cut.indexOf("{")
|
|
79
|
+
const semi = cut.indexOf(";")
|
|
80
|
+
if (brace !== -1 && (semi === -1 || brace < semi)) {
|
|
81
|
+
cut = cut.slice(0, brace)
|
|
82
|
+
} else if (semi !== -1) {
|
|
83
|
+
cut = cut.slice(0, semi)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return stripLeadingKeywords(cut.replace(/\s+/g, " ").trim())
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Leading tokens to drop from a signature: visibility/export modifiers and the
|
|
91
|
+
* declaration keyword itself (`export function foo()`, `abstract class Shape`,
|
|
92
|
+
* `public readonly x`). The kind column already prints the keyword, so keeping
|
|
93
|
+
* it would duplicate ("function export function foo()").
|
|
94
|
+
*/
|
|
95
|
+
const STRIP_PREFIX = [
|
|
96
|
+
"export", "default", "declare", "abstract", "async",
|
|
97
|
+
"public", "private", "protected", "readonly", "static", "override",
|
|
98
|
+
"function", "class", "interface", "type", "enum", "namespace", "module",
|
|
99
|
+
"const", "let", "var",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
/** Strip leading modifier/kind tokens, keeping the name onward intact. */
|
|
103
|
+
function stripLeadingKeywords(text: string): string {
|
|
104
|
+
const words = text.split(/\s+/)
|
|
105
|
+
let i = 0
|
|
106
|
+
while (i < words.length && STRIP_PREFIX.includes(words[i]!)) {
|
|
107
|
+
i++
|
|
108
|
+
}
|
|
109
|
+
return words.slice(i).join(" ")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Variable statements declare each name separately (kind from the statement). */
|
|
113
|
+
function variableEntries(stmt: ts.VariableStatement, sourceFile: ts.SourceFile): OutlineEntry[] {
|
|
114
|
+
const flags = stmt.declarationList.flags
|
|
115
|
+
const kind = flags & ts.NodeFlags.Const ? "const" : flags & ts.NodeFlags.Let ? "let" : "var"
|
|
116
|
+
return stmt.declarationList.declarations.map((decl) => {
|
|
117
|
+
const name = decl.name.getText(sourceFile)
|
|
118
|
+
const type = decl.type !== undefined ? decl.type.getText(sourceFile) : undefined
|
|
119
|
+
return {
|
|
120
|
+
line: ts.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).line + 1,
|
|
121
|
+
kind,
|
|
122
|
+
// The kind column already prints the keyword — don't repeat it.
|
|
123
|
+
signature: `${name}${type !== undefined ? `: ${type}` : ""}`,
|
|
124
|
+
depth: 0,
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Outline a source file. `maxDepth` controls how deep into the declaration
|
|
131
|
+
* tree we go: 0 = top level only, 1 (default) = also members of classes,
|
|
132
|
+
* interfaces, and namespaces. Function bodies are never descended into —
|
|
133
|
+
* nested locals are noise for a structure overview.
|
|
134
|
+
*/
|
|
135
|
+
export function outlineSourceFile(sourceFile: ts.SourceFile, maxDepth = 1): OutlineEntry[] {
|
|
136
|
+
const entries: OutlineEntry[] = []
|
|
137
|
+
|
|
138
|
+
const walk = (node: ts.Node, depth: number): void => {
|
|
139
|
+
if (ts.isVariableStatement(node)) {
|
|
140
|
+
entries.push(...variableEntries(node, sourceFile))
|
|
141
|
+
} else {
|
|
142
|
+
const kind = kindOf(node)
|
|
143
|
+
if (kind !== undefined) {
|
|
144
|
+
const named = node as { name?: ts.Node }
|
|
145
|
+
entries.push({
|
|
146
|
+
line:
|
|
147
|
+
ts.getLineAndCharacterOfPosition(sourceFile, named.name ? named.name.getStart(sourceFile) : node.getStart(sourceFile))
|
|
148
|
+
.line + 1,
|
|
149
|
+
kind,
|
|
150
|
+
signature: signatureOf(node, sourceFile),
|
|
151
|
+
depth,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (depth >= maxDepth) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
// Never look inside function bodies; containers get one deeper level.
|
|
160
|
+
if (hasBlockBody(node) || ts.isVariableStatement(node)) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (isContainer(node)) {
|
|
164
|
+
ts.forEachChild(node, (child) => walk(child, depth + 1))
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
ts.forEachChild(node, (child) => walk(child, depth))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
ts.forEachChild(sourceFile, (child) => walk(child, 0))
|
|
171
|
+
return entries
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Render outline entries as the compact `line kind signature` lines. */
|
|
175
|
+
export function formatOutline(entries: OutlineEntry[]): string {
|
|
176
|
+
if (entries.length === 0) {
|
|
177
|
+
return "No declarations found in this file."
|
|
178
|
+
}
|
|
179
|
+
return entries.map((e) => `${" ".repeat(e.depth)}${e.line} ${e.kind} ${e.signature}`).join("\n")
|
|
180
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Position → identifier resolution shared by `go_to_definition` and
|
|
3
|
+
* `find_references`.
|
|
4
|
+
*
|
|
5
|
+
* The TS LanguageService APIs (`getDefinitionAtPosition`,
|
|
6
|
+
* `getReferencesAtPosition`) take a character OFFSET into the file, not a
|
|
7
|
+
* line number — so the tools' `{ line, character?, symbol? }` parameter
|
|
8
|
+
* shape is resolved here into a concrete `ts.Identifier` the LS can answer
|
|
9
|
+
* about:
|
|
10
|
+
* - `character` given (1-based column): the identifier touching that column
|
|
11
|
+
* - `symbol` given: the first identifier on the line whose text matches
|
|
12
|
+
* - neither: the first identifier on the line
|
|
13
|
+
*
|
|
14
|
+
* Identifiers come from a real AST walk (`ts.forEachChild`), so text inside
|
|
15
|
+
* strings, comments, and import specifiers is naturally excluded — except
|
|
16
|
+
* import clause names (`import { foo }` parses `foo` as an Identifier), which
|
|
17
|
+
* is exactly what we want: "go to definition" on an imported name must
|
|
18
|
+
* resolve through the checker to its real declaration.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as ts from "typescript"
|
|
22
|
+
|
|
23
|
+
/** Collect every Identifier whose text range falls on `line` (1-based). */
|
|
24
|
+
export function identifiersOnLine(sourceFile: ts.SourceFile, line: number): ts.Identifier[] {
|
|
25
|
+
const line0 = line - 1
|
|
26
|
+
const found: ts.Identifier[] = []
|
|
27
|
+
const visit = (node: ts.Node): void => {
|
|
28
|
+
if (ts.isIdentifier(node)) {
|
|
29
|
+
const { line: nodeLine } = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile))
|
|
30
|
+
if (nodeLine === line0) {
|
|
31
|
+
found.push(node)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
ts.forEachChild(node, visit)
|
|
35
|
+
}
|
|
36
|
+
visit(sourceFile)
|
|
37
|
+
return found
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a (1-based line, optional 1-based character) + optional symbol name
|
|
42
|
+
* to the identifier a user means, or undefined when nothing resolvable sits
|
|
43
|
+
* at that spot. "Touching" semantics: a character pointing at the LAST char
|
|
44
|
+
* of an identifier still selects it, so a model that guessed the column one
|
|
45
|
+
* short of the end still gets the right symbol.
|
|
46
|
+
*/
|
|
47
|
+
export function identifierAtPosition(
|
|
48
|
+
sourceFile: ts.SourceFile,
|
|
49
|
+
line: number,
|
|
50
|
+
character: number | undefined,
|
|
51
|
+
symbol: string | undefined,
|
|
52
|
+
): ts.Identifier | undefined {
|
|
53
|
+
if (line < 1 || line > sourceFile.getLineStarts().length) {
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
const onLine = identifiersOnLine(sourceFile, line)
|
|
57
|
+
if (onLine.length === 0) {
|
|
58
|
+
return undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (character !== undefined && character >= 1) {
|
|
62
|
+
const lineStart = ts.getPositionOfLineAndCharacter(sourceFile, line - 1, 0)
|
|
63
|
+
const pos = lineStart + (character - 1)
|
|
64
|
+
for (const id of onLine) {
|
|
65
|
+
if (pos >= id.getStart(sourceFile) && pos <= id.getEnd()) {
|
|
66
|
+
return id
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (symbol !== undefined && symbol.trim() !== "") {
|
|
73
|
+
return onLine.find((id) => id.text === symbol)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return onLine[0]
|
|
77
|
+
}
|