@claudiu-ceia/astkit 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.
- package/LICENSE +21 -0
- package/README.md +393 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/src/app.d.ts +1 -0
- package/dist/src/app.js +41 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +4 -0
- package/dist/src/code-rank/code-rank.d.ts +9 -0
- package/dist/src/code-rank/code-rank.js +71 -0
- package/dist/src/code-rank/index.d.ts +1 -0
- package/dist/src/code-rank/index.js +1 -0
- package/dist/src/code-rank/rank.d.ts +29 -0
- package/dist/src/code-rank/rank.js +185 -0
- package/dist/src/common/input.d.ts +5 -0
- package/dist/src/common/input.js +25 -0
- package/dist/src/nav/declarations.d.ts +19 -0
- package/dist/src/nav/declarations.js +106 -0
- package/dist/src/nav/definition.d.ts +14 -0
- package/dist/src/nav/definition.js +62 -0
- package/dist/src/nav/location.d.ts +6 -0
- package/dist/src/nav/location.js +35 -0
- package/dist/src/nav/references.d.ts +19 -0
- package/dist/src/nav/references.js +75 -0
- package/dist/src/patch/patch.d.ts +27 -0
- package/dist/src/patch/patch.js +345 -0
- package/dist/src/pattern/balance.d.ts +1 -0
- package/dist/src/pattern/balance.js +25 -0
- package/dist/src/pattern/index.d.ts +5 -0
- package/dist/src/pattern/index.js +4 -0
- package/dist/src/pattern/match.d.ts +2 -0
- package/dist/src/pattern/match.js +141 -0
- package/dist/src/pattern/render.d.ts +1 -0
- package/dist/src/pattern/render.js +32 -0
- package/dist/src/pattern/syntax.d.ts +3 -0
- package/dist/src/pattern/syntax.js +87 -0
- package/dist/src/pattern/types.d.ts +27 -0
- package/dist/src/pattern/types.js +1 -0
- package/dist/src/search/search.d.ts +16 -0
- package/dist/src/search/search.js +207 -0
- package/dist/src/service.d.ts +16 -0
- package/dist/src/service.js +72 -0
- package/dist/src/sgrep/index.d.ts +5 -0
- package/dist/src/sgrep/index.js +3 -0
- package/dist/src/sgrep/isomorphisms/expand.d.ts +2 -0
- package/dist/src/sgrep/isomorphisms/expand.js +51 -0
- package/dist/src/sgrep/isomorphisms/index.d.ts +3 -0
- package/dist/src/sgrep/isomorphisms/index.js +2 -0
- package/dist/src/sgrep/isomorphisms/registry.d.ts +2 -0
- package/dist/src/sgrep/isomorphisms/registry.js +8 -0
- package/dist/src/sgrep/isomorphisms/rules/commutative-binary.d.ts +2 -0
- package/dist/src/sgrep/isomorphisms/rules/commutative-binary.js +51 -0
- package/dist/src/sgrep/isomorphisms/rules/object-literal-property-order.d.ts +2 -0
- package/dist/src/sgrep/isomorphisms/rules/object-literal-property-order.js +82 -0
- package/dist/src/sgrep/isomorphisms/rules/redundant-parentheses.d.ts +2 -0
- package/dist/src/sgrep/isomorphisms/rules/redundant-parentheses.js +43 -0
- package/dist/src/sgrep/isomorphisms/template-ast.d.ts +2 -0
- package/dist/src/sgrep/isomorphisms/template-ast.js +59 -0
- package/dist/src/sgrep/isomorphisms/types.d.ts +15 -0
- package/dist/src/sgrep/isomorphisms/types.js +0 -0
- package/dist/src/sgrep/phases/output.d.ts +9 -0
- package/dist/src/sgrep/phases/output.js +11 -0
- package/dist/src/sgrep/phases/parse.d.ts +10 -0
- package/dist/src/sgrep/phases/parse.js +11 -0
- package/dist/src/sgrep/phases/search.d.ts +11 -0
- package/dist/src/sgrep/phases/search.js +111 -0
- package/dist/src/sgrep/sgrep.d.ts +3 -0
- package/dist/src/sgrep/sgrep.js +17 -0
- package/dist/src/sgrep/types.d.ts +32 -0
- package/dist/src/sgrep/types.js +3 -0
- package/dist/src/spatch/files.d.ts +7 -0
- package/dist/src/spatch/files.js +51 -0
- package/dist/src/spatch/index.d.ts +3 -0
- package/dist/src/spatch/index.js +2 -0
- package/dist/src/spatch/patch-document.d.ts +9 -0
- package/dist/src/spatch/patch-document.js +64 -0
- package/dist/src/spatch/phases/output.d.ts +9 -0
- package/dist/src/spatch/phases/output.js +15 -0
- package/dist/src/spatch/phases/parse.d.ts +11 -0
- package/dist/src/spatch/phases/parse.js +16 -0
- package/dist/src/spatch/phases/rewrite.d.ts +14 -0
- package/dist/src/spatch/phases/rewrite.js +111 -0
- package/dist/src/spatch/spatch.d.ts +3 -0
- package/dist/src/spatch/spatch.js +17 -0
- package/dist/src/spatch/template.d.ts +2 -0
- package/dist/src/spatch/template.js +1 -0
- package/dist/src/spatch/text.d.ts +5 -0
- package/dist/src/spatch/text.js +35 -0
- package/dist/src/spatch/types.d.ts +40 -0
- package/dist/src/spatch/types.js +20 -0
- package/package.json +69 -0
- package/skills/astkit-tooling/SKILL.md +101 -0
- package/skills/astkit-tooling/agents/openai.yaml +4 -0
- package/skills/astkit-tooling/references/cognitive-model.md +61 -0
- package/skills/astkit-tooling/references/non-goals.md +11 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { createService, fromPosition, relativePath } from "../service.js";
|
|
4
|
+
import { collectPatchableFiles } from "../spatch/files.js";
|
|
5
|
+
import { DEFAULT_EXCLUDED_DIRECTORIES, DEFAULT_PATCHABLE_EXTENSIONS, } from "../spatch/types.js";
|
|
6
|
+
export async function rankCode(options = {}) {
|
|
7
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
8
|
+
const scope = options.scope ?? ".";
|
|
9
|
+
const resolvedScope = path.resolve(cwd, scope);
|
|
10
|
+
const files = await collectPatchableFiles({
|
|
11
|
+
cwd,
|
|
12
|
+
scope,
|
|
13
|
+
extensions: options.extensions ?? DEFAULT_PATCHABLE_EXTENSIONS,
|
|
14
|
+
excludedDirectories: options.excludedDirectories ?? DEFAULT_EXCLUDED_DIRECTORIES,
|
|
15
|
+
});
|
|
16
|
+
if (files.length === 0) {
|
|
17
|
+
return {
|
|
18
|
+
cwd,
|
|
19
|
+
scope: resolvedScope,
|
|
20
|
+
filesScanned: 0,
|
|
21
|
+
symbolsScanned: 0,
|
|
22
|
+
symbolsRanked: 0,
|
|
23
|
+
symbols: [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const { service, program, projectRoot } = createService(cwd, files);
|
|
27
|
+
const checker = program.getTypeChecker();
|
|
28
|
+
const symbols = [];
|
|
29
|
+
const seenDeclarations = new Set();
|
|
30
|
+
for (const filePath of files) {
|
|
31
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
32
|
+
if (!sourceFile) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
36
|
+
if (!moduleSymbol) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const exportedSymbols = checker.getExportsOfModule(moduleSymbol);
|
|
40
|
+
for (const exportedSymbol of exportedSymbols) {
|
|
41
|
+
const declaration = pickDeclarationInFile(exportedSymbol, filePath);
|
|
42
|
+
if (!declaration) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const declarationSourceFile = declaration.getSourceFile();
|
|
46
|
+
const declarationStart = declaration.getStart(declarationSourceFile);
|
|
47
|
+
const declarationKey = `${declarationSourceFile.fileName}:${declarationStart}:${exportedSymbol.getName()}`;
|
|
48
|
+
if (seenDeclarations.has(declarationKey)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
seenDeclarations.add(declarationKey);
|
|
52
|
+
const stats = collectReferenceStats(service.findReferences(declarationSourceFile.fileName, declarationStart), declarationSourceFile.fileName, projectRoot);
|
|
53
|
+
const pos = fromPosition(declarationSourceFile, declarationStart);
|
|
54
|
+
symbols.push({
|
|
55
|
+
symbol: exportedSymbol.getName(),
|
|
56
|
+
kind: getDeclarationKind(declaration),
|
|
57
|
+
file: relativePath(projectRoot, declarationSourceFile.fileName) ||
|
|
58
|
+
path.basename(declarationSourceFile.fileName),
|
|
59
|
+
line: pos.line,
|
|
60
|
+
character: pos.character,
|
|
61
|
+
score: computeRankScore(stats),
|
|
62
|
+
referenceCount: stats.referenceCount,
|
|
63
|
+
internalReferenceCount: stats.internalReferenceCount,
|
|
64
|
+
externalReferenceCount: stats.externalReferenceCount,
|
|
65
|
+
referencingFileCount: stats.referencingFiles.length,
|
|
66
|
+
referencingFiles: stats.referencingFiles,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
symbols.sort(compareRankedSymbols);
|
|
71
|
+
const limit = normalizeLimit(options.limit);
|
|
72
|
+
const rankedSymbols = limit === null ? symbols : symbols.slice(0, limit);
|
|
73
|
+
return {
|
|
74
|
+
cwd,
|
|
75
|
+
scope: resolvedScope,
|
|
76
|
+
filesScanned: files.length,
|
|
77
|
+
symbolsScanned: symbols.length,
|
|
78
|
+
symbolsRanked: rankedSymbols.length,
|
|
79
|
+
symbols: rankedSymbols,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function collectReferenceStats(references, declarationFile, projectRoot) {
|
|
83
|
+
if (!references || references.length === 0) {
|
|
84
|
+
return {
|
|
85
|
+
referenceCount: 0,
|
|
86
|
+
internalReferenceCount: 0,
|
|
87
|
+
externalReferenceCount: 0,
|
|
88
|
+
referencingFiles: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const seenReferences = new Set();
|
|
92
|
+
const referencingFiles = new Set();
|
|
93
|
+
const normalizedDeclarationFile = path.resolve(declarationFile);
|
|
94
|
+
let referenceCount = 0;
|
|
95
|
+
let internalReferenceCount = 0;
|
|
96
|
+
let externalReferenceCount = 0;
|
|
97
|
+
for (const group of references) {
|
|
98
|
+
for (const reference of group.references) {
|
|
99
|
+
const key = `${reference.fileName}:${reference.textSpan.start}:${reference.textSpan.length}`;
|
|
100
|
+
if (seenReferences.has(key)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
seenReferences.add(key);
|
|
104
|
+
if (reference.isDefinition) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
referenceCount += 1;
|
|
108
|
+
const normalizedReferenceFile = path.resolve(reference.fileName);
|
|
109
|
+
if (normalizedReferenceFile === normalizedDeclarationFile) {
|
|
110
|
+
internalReferenceCount += 1;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
externalReferenceCount += 1;
|
|
114
|
+
}
|
|
115
|
+
referencingFiles.add(relativePath(projectRoot, reference.fileName) ||
|
|
116
|
+
path.basename(reference.fileName));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
referenceCount,
|
|
121
|
+
internalReferenceCount,
|
|
122
|
+
externalReferenceCount,
|
|
123
|
+
referencingFiles: [...referencingFiles].sort((left, right) => left.localeCompare(right)),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function computeRankScore(stats) {
|
|
127
|
+
return (stats.externalReferenceCount * 4 +
|
|
128
|
+
stats.referenceCount +
|
|
129
|
+
stats.referencingFiles.length * 2);
|
|
130
|
+
}
|
|
131
|
+
function compareRankedSymbols(left, right) {
|
|
132
|
+
return (right.score - left.score ||
|
|
133
|
+
right.externalReferenceCount - left.externalReferenceCount ||
|
|
134
|
+
right.referenceCount - left.referenceCount ||
|
|
135
|
+
left.file.localeCompare(right.file) ||
|
|
136
|
+
left.line - right.line ||
|
|
137
|
+
left.symbol.localeCompare(right.symbol));
|
|
138
|
+
}
|
|
139
|
+
function pickDeclarationInFile(symbol, filePath) {
|
|
140
|
+
const normalizedFilePath = path.resolve(filePath);
|
|
141
|
+
const declarations = symbol.getDeclarations();
|
|
142
|
+
if (!declarations || declarations.length === 0) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
for (const declaration of declarations) {
|
|
146
|
+
const declarationFile = path.resolve(declaration.getSourceFile().fileName);
|
|
147
|
+
if (declarationFile === normalizedFilePath &&
|
|
148
|
+
isRankableDeclaration(declaration)) {
|
|
149
|
+
return declaration;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function getDeclarationKind(declaration) {
|
|
155
|
+
if (ts.isFunctionDeclaration(declaration))
|
|
156
|
+
return "function";
|
|
157
|
+
if (ts.isClassDeclaration(declaration))
|
|
158
|
+
return "class";
|
|
159
|
+
if (ts.isInterfaceDeclaration(declaration))
|
|
160
|
+
return "interface";
|
|
161
|
+
if (ts.isTypeAliasDeclaration(declaration))
|
|
162
|
+
return "type";
|
|
163
|
+
if (ts.isEnumDeclaration(declaration))
|
|
164
|
+
return "enum";
|
|
165
|
+
if (ts.isVariableDeclaration(declaration))
|
|
166
|
+
return "const";
|
|
167
|
+
if (ts.isModuleDeclaration(declaration))
|
|
168
|
+
return "module";
|
|
169
|
+
return "unknown";
|
|
170
|
+
}
|
|
171
|
+
function isRankableDeclaration(declaration) {
|
|
172
|
+
return (ts.isFunctionDeclaration(declaration) ||
|
|
173
|
+
ts.isClassDeclaration(declaration) ||
|
|
174
|
+
ts.isInterfaceDeclaration(declaration) ||
|
|
175
|
+
ts.isTypeAliasDeclaration(declaration) ||
|
|
176
|
+
ts.isEnumDeclaration(declaration) ||
|
|
177
|
+
ts.isVariableDeclaration(declaration) ||
|
|
178
|
+
ts.isModuleDeclaration(declaration));
|
|
179
|
+
}
|
|
180
|
+
function normalizeLimit(limit) {
|
|
181
|
+
if (limit === undefined) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return Math.max(0, Math.floor(limit));
|
|
185
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function resolveTextInput(input, options = {}) {
|
|
4
|
+
if (input.includes("\n") || input.includes("\r")) {
|
|
5
|
+
return input;
|
|
6
|
+
}
|
|
7
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
8
|
+
const inputPath = path.resolve(cwd, input);
|
|
9
|
+
try {
|
|
10
|
+
const inputStats = await stat(inputPath);
|
|
11
|
+
if (!inputStats.isFile()) {
|
|
12
|
+
throw new Error(`Input path is not a file: ${inputPath}`);
|
|
13
|
+
}
|
|
14
|
+
return await readFile(inputPath, options.encoding ?? "utf8");
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (isErrorWithCode(error) && error.code === "ENOENT") {
|
|
18
|
+
return input;
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isErrorWithCode(error) {
|
|
24
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface MemberInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
signature: string;
|
|
4
|
+
line: number;
|
|
5
|
+
}
|
|
6
|
+
interface DeclarationInfo {
|
|
7
|
+
name: string;
|
|
8
|
+
kind: string;
|
|
9
|
+
signature: string;
|
|
10
|
+
line: number;
|
|
11
|
+
members?: MemberInfo[];
|
|
12
|
+
}
|
|
13
|
+
interface DeclarationsOutput {
|
|
14
|
+
file: string;
|
|
15
|
+
declarations: DeclarationInfo[];
|
|
16
|
+
}
|
|
17
|
+
export declare function getDeclarations(filePath: string): DeclarationsOutput;
|
|
18
|
+
export declare const declarationsCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildCommand } from "@stricli/core";
|
|
4
|
+
import { createService, fromPosition, relativePath } from "../service.js";
|
|
5
|
+
function getDeclarationKind(declaration) {
|
|
6
|
+
if (ts.isFunctionDeclaration(declaration))
|
|
7
|
+
return "function";
|
|
8
|
+
if (ts.isClassDeclaration(declaration))
|
|
9
|
+
return "class";
|
|
10
|
+
if (ts.isInterfaceDeclaration(declaration))
|
|
11
|
+
return "interface";
|
|
12
|
+
if (ts.isTypeAliasDeclaration(declaration))
|
|
13
|
+
return "type";
|
|
14
|
+
if (ts.isEnumDeclaration(declaration))
|
|
15
|
+
return "enum";
|
|
16
|
+
if (ts.isVariableDeclaration(declaration))
|
|
17
|
+
return "const";
|
|
18
|
+
if (ts.isModuleDeclaration(declaration))
|
|
19
|
+
return "module";
|
|
20
|
+
return "unknown";
|
|
21
|
+
}
|
|
22
|
+
export function getDeclarations(filePath) {
|
|
23
|
+
const resolved = path.resolve(filePath);
|
|
24
|
+
const { program, projectRoot } = createService(process.cwd(), resolved);
|
|
25
|
+
const sourceFile = program.getSourceFile(resolved);
|
|
26
|
+
if (!sourceFile) {
|
|
27
|
+
throw new Error(`File not found: ${filePath}`);
|
|
28
|
+
}
|
|
29
|
+
const typeChecker = program.getTypeChecker();
|
|
30
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
31
|
+
if (!moduleSymbol) {
|
|
32
|
+
return { file: relativePath(projectRoot, resolved), declarations: [] };
|
|
33
|
+
}
|
|
34
|
+
const exports = typeChecker.getExportsOfModule(moduleSymbol);
|
|
35
|
+
const declarations = [];
|
|
36
|
+
for (const exp of exports) {
|
|
37
|
+
const decls = exp.getDeclarations();
|
|
38
|
+
if (!decls || decls.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const declaration = decls[0];
|
|
41
|
+
// Only include declarations from the target file
|
|
42
|
+
if (declaration.getSourceFile().fileName !== resolved)
|
|
43
|
+
continue;
|
|
44
|
+
const kind = getDeclarationKind(declaration);
|
|
45
|
+
const type = (kind === "interface" || kind === "class" || kind === "enum" || kind === "type")
|
|
46
|
+
? typeChecker.getDeclaredTypeOfSymbol(exp)
|
|
47
|
+
: typeChecker.getTypeOfSymbol(exp);
|
|
48
|
+
const signature = typeChecker.typeToString(type, declaration, ts.TypeFormatFlags.NoTruncation);
|
|
49
|
+
const pos = fromPosition(sourceFile, declaration.getStart(sourceFile));
|
|
50
|
+
const info = {
|
|
51
|
+
name: exp.getName(),
|
|
52
|
+
kind,
|
|
53
|
+
signature,
|
|
54
|
+
line: pos.line,
|
|
55
|
+
};
|
|
56
|
+
// For classes and interfaces, enumerate members
|
|
57
|
+
if (kind === "class" || kind === "interface") {
|
|
58
|
+
const declType = typeChecker.getDeclaredTypeOfSymbol(exp);
|
|
59
|
+
const properties = typeChecker.getPropertiesOfType(declType);
|
|
60
|
+
const members = [];
|
|
61
|
+
for (const prop of properties) {
|
|
62
|
+
const propDecls = prop.getDeclarations();
|
|
63
|
+
if (!propDecls || propDecls.length === 0)
|
|
64
|
+
continue;
|
|
65
|
+
const propDecl = propDecls[0];
|
|
66
|
+
// Only include members from this file
|
|
67
|
+
if (propDecl.getSourceFile().fileName !== resolved)
|
|
68
|
+
continue;
|
|
69
|
+
const propType = typeChecker.getTypeOfSymbol(prop);
|
|
70
|
+
const propSignature = typeChecker.typeToString(propType, propDecl, ts.TypeFormatFlags.NoTruncation);
|
|
71
|
+
const propPos = fromPosition(sourceFile, propDecl.getStart(sourceFile));
|
|
72
|
+
members.push({
|
|
73
|
+
name: prop.getName(),
|
|
74
|
+
signature: propSignature,
|
|
75
|
+
line: propPos.line,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (members.length > 0) {
|
|
79
|
+
info.members = members;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
declarations.push(info);
|
|
83
|
+
}
|
|
84
|
+
return { file: relativePath(projectRoot, resolved), declarations };
|
|
85
|
+
}
|
|
86
|
+
export const declarationsCommand = buildCommand({
|
|
87
|
+
func(_flags, file) {
|
|
88
|
+
const result = getDeclarations(file);
|
|
89
|
+
this.process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
90
|
+
},
|
|
91
|
+
parameters: {
|
|
92
|
+
positional: {
|
|
93
|
+
kind: "tuple",
|
|
94
|
+
parameters: [
|
|
95
|
+
{
|
|
96
|
+
brief: "File to list declarations from",
|
|
97
|
+
placeholder: "file",
|
|
98
|
+
parse: (input) => input,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
docs: {
|
|
104
|
+
brief: "List exported declarations and type signatures",
|
|
105
|
+
},
|
|
106
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface DefinitionLocation {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
character: number;
|
|
5
|
+
kind: string;
|
|
6
|
+
containerName: string;
|
|
7
|
+
}
|
|
8
|
+
interface DefinitionOutput {
|
|
9
|
+
symbol: string;
|
|
10
|
+
definitions: DefinitionLocation[];
|
|
11
|
+
}
|
|
12
|
+
export declare function getDefinition(filePath: string, line: number, character: number): DefinitionOutput;
|
|
13
|
+
export declare const definitionCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { buildCommand } from "@stricli/core";
|
|
3
|
+
import { parseFilePosition } from "./location.js";
|
|
4
|
+
import { createService, toPosition, fromPosition, relativePath } from "../service.js";
|
|
5
|
+
export function getDefinition(filePath, line, character) {
|
|
6
|
+
const resolved = path.resolve(filePath);
|
|
7
|
+
const { service, program, projectRoot } = createService(process.cwd(), resolved);
|
|
8
|
+
const sourceFile = program.getSourceFile(resolved);
|
|
9
|
+
if (!sourceFile) {
|
|
10
|
+
throw new Error(`File not found: ${filePath}`);
|
|
11
|
+
}
|
|
12
|
+
const pos = toPosition(sourceFile, line, character);
|
|
13
|
+
const defs = service.getDefinitionAtPosition(resolved, pos);
|
|
14
|
+
if (!defs || defs.length === 0) {
|
|
15
|
+
// Get the word at position for the symbol name
|
|
16
|
+
const wordRange = service.getSmartSelectionRange(resolved, pos);
|
|
17
|
+
const symbolName = wordRange
|
|
18
|
+
? sourceFile.text.slice(wordRange.textSpan.start, wordRange.textSpan.start + wordRange.textSpan.length)
|
|
19
|
+
: "<unknown>";
|
|
20
|
+
return { symbol: symbolName, definitions: [] };
|
|
21
|
+
}
|
|
22
|
+
const symbol = defs[0].name || "<unknown>";
|
|
23
|
+
const definitions = defs.map((def) => {
|
|
24
|
+
const defSourceFile = program.getSourceFile(def.fileName);
|
|
25
|
+
let defLine = 1;
|
|
26
|
+
let defChar = 1;
|
|
27
|
+
if (defSourceFile) {
|
|
28
|
+
const lc = fromPosition(defSourceFile, def.textSpan.start);
|
|
29
|
+
defLine = lc.line;
|
|
30
|
+
defChar = lc.character;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
file: relativePath(projectRoot, def.fileName),
|
|
34
|
+
line: defLine,
|
|
35
|
+
character: defChar,
|
|
36
|
+
kind: def.kind,
|
|
37
|
+
containerName: def.containerName || "",
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
return { symbol, definitions };
|
|
41
|
+
}
|
|
42
|
+
export const definitionCommand = buildCommand({
|
|
43
|
+
func(_flags, location) {
|
|
44
|
+
const result = getDefinition(location.file, location.line, location.character);
|
|
45
|
+
this.process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
46
|
+
},
|
|
47
|
+
parameters: {
|
|
48
|
+
positional: {
|
|
49
|
+
kind: "tuple",
|
|
50
|
+
parameters: [
|
|
51
|
+
{
|
|
52
|
+
brief: "Location (<file>:<line>:<character>)",
|
|
53
|
+
placeholder: "location",
|
|
54
|
+
parse: parseFilePosition,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
docs: {
|
|
60
|
+
brief: "Go to definition at position",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { eof, map, regex as parseRegex, seq, str, } from "@claudiu-ceia/combine";
|
|
2
|
+
const LOCATION_ERROR_MESSAGE = "Invalid location: expected <file>:<line>:<character>.";
|
|
3
|
+
const positiveIntegerParser = parseRegex(/[1-9][0-9]*/, "positive integer");
|
|
4
|
+
const fileForColonSyntaxParser = parseRegex(/[^\n]+(?=:[1-9][0-9]*:[1-9][0-9]*$)/, "file path");
|
|
5
|
+
const colonFilePositionParser = map(seq(fileForColonSyntaxParser, str(":"), positiveIntegerParser, str(":"), positiveIntegerParser, eof()), ([file, , line, , character]) => ({
|
|
6
|
+
file: file.trim(),
|
|
7
|
+
line: parsePositiveInteger(line, "line"),
|
|
8
|
+
character: parsePositiveInteger(character, "character"),
|
|
9
|
+
}));
|
|
10
|
+
export function parseFilePosition(input) {
|
|
11
|
+
const raw = input.trim();
|
|
12
|
+
if (raw.length === 0) {
|
|
13
|
+
throw new Error(LOCATION_ERROR_MESSAGE);
|
|
14
|
+
}
|
|
15
|
+
const parsed = colonFilePositionParser({ text: raw, index: 0 });
|
|
16
|
+
if (!parsed.success) {
|
|
17
|
+
throw new Error(LOCATION_ERROR_MESSAGE);
|
|
18
|
+
}
|
|
19
|
+
const file = parsed.value.file.trim();
|
|
20
|
+
if (file.length === 0) {
|
|
21
|
+
throw new Error("Invalid file: file path cannot be empty.");
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
file,
|
|
25
|
+
line: parsed.value.line,
|
|
26
|
+
character: parsed.value.character,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function parsePositiveInteger(raw, label) {
|
|
30
|
+
const parsed = Number.parseInt(raw, 10);
|
|
31
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
32
|
+
throw new Error(`Invalid ${label}: expected a positive integer.`);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface ReferenceLocation {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
character: number;
|
|
5
|
+
isDefinition: boolean;
|
|
6
|
+
isWriteAccess: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ReferencesOutput {
|
|
9
|
+
symbol: string;
|
|
10
|
+
definition: {
|
|
11
|
+
file: string;
|
|
12
|
+
line: number;
|
|
13
|
+
character: number;
|
|
14
|
+
} | null;
|
|
15
|
+
references: ReferenceLocation[];
|
|
16
|
+
}
|
|
17
|
+
export declare function getReferences(filePath: string, line: number, character: number): ReferencesOutput;
|
|
18
|
+
export declare const referencesCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { buildCommand } from "@stricli/core";
|
|
3
|
+
import { parseFilePosition } from "./location.js";
|
|
4
|
+
import { createService, toPosition, fromPosition, relativePath } from "../service.js";
|
|
5
|
+
export function getReferences(filePath, line, character) {
|
|
6
|
+
const resolved = path.resolve(filePath);
|
|
7
|
+
const { service, program, projectRoot } = createService(process.cwd(), resolved);
|
|
8
|
+
const sourceFile = program.getSourceFile(resolved);
|
|
9
|
+
if (!sourceFile) {
|
|
10
|
+
throw new Error(`File not found: ${filePath}`);
|
|
11
|
+
}
|
|
12
|
+
const pos = toPosition(sourceFile, line, character);
|
|
13
|
+
const refSymbols = service.findReferences(resolved, pos);
|
|
14
|
+
if (!refSymbols || refSymbols.length === 0) {
|
|
15
|
+
const wordRange = service.getSmartSelectionRange(resolved, pos);
|
|
16
|
+
const symbolName = wordRange
|
|
17
|
+
? sourceFile.text.slice(wordRange.textSpan.start, wordRange.textSpan.start + wordRange.textSpan.length)
|
|
18
|
+
: "<unknown>";
|
|
19
|
+
return { symbol: symbolName, definition: null, references: [] };
|
|
20
|
+
}
|
|
21
|
+
const firstGroup = refSymbols[0];
|
|
22
|
+
const symbol = firstGroup.definition.name || "<unknown>";
|
|
23
|
+
// Extract definition location
|
|
24
|
+
const defSpan = firstGroup.definition.textSpan;
|
|
25
|
+
const defFile = firstGroup.definition.fileName;
|
|
26
|
+
const defSourceFile = program.getSourceFile(defFile);
|
|
27
|
+
let definition = null;
|
|
28
|
+
if (defSourceFile) {
|
|
29
|
+
const defPos = fromPosition(defSourceFile, defSpan.start);
|
|
30
|
+
definition = {
|
|
31
|
+
file: relativePath(projectRoot, defFile),
|
|
32
|
+
line: defPos.line,
|
|
33
|
+
character: defPos.character,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Extract all references
|
|
37
|
+
const references = [];
|
|
38
|
+
for (const group of refSymbols) {
|
|
39
|
+
for (const ref of group.references) {
|
|
40
|
+
const refSourceFile = program.getSourceFile(ref.fileName);
|
|
41
|
+
if (!refSourceFile)
|
|
42
|
+
continue;
|
|
43
|
+
const refPos = fromPosition(refSourceFile, ref.textSpan.start);
|
|
44
|
+
references.push({
|
|
45
|
+
file: relativePath(projectRoot, ref.fileName),
|
|
46
|
+
line: refPos.line,
|
|
47
|
+
character: refPos.character,
|
|
48
|
+
isDefinition: ref.isDefinition ?? false,
|
|
49
|
+
isWriteAccess: ref.isWriteAccess ?? false,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { symbol, definition, references };
|
|
54
|
+
}
|
|
55
|
+
export const referencesCommand = buildCommand({
|
|
56
|
+
func(_flags, location) {
|
|
57
|
+
const result = getReferences(location.file, location.line, location.character);
|
|
58
|
+
this.process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
59
|
+
},
|
|
60
|
+
parameters: {
|
|
61
|
+
positional: {
|
|
62
|
+
kind: "tuple",
|
|
63
|
+
parameters: [
|
|
64
|
+
{
|
|
65
|
+
brief: "Location (<file>:<line>:<character>)",
|
|
66
|
+
placeholder: "location",
|
|
67
|
+
parse: parseFilePosition,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
docs: {
|
|
73
|
+
brief: "Find all references at position",
|
|
74
|
+
},
|
|
75
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ChalkInstance } from "chalk";
|
|
2
|
+
import type { SpatchOccurrence, SpatchResult } from "../spatch/types.ts";
|
|
3
|
+
export type PatchCommandFlags = {
|
|
4
|
+
"dry-run"?: boolean;
|
|
5
|
+
interactive?: boolean;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
"no-color"?: boolean;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
};
|
|
10
|
+
type InteractiveChoice = "yes" | "no" | "all" | "quit";
|
|
11
|
+
export type InteractiveContext = {
|
|
12
|
+
file: string;
|
|
13
|
+
occurrence: SpatchOccurrence;
|
|
14
|
+
changeNumber: number;
|
|
15
|
+
totalChanges: number;
|
|
16
|
+
};
|
|
17
|
+
export type RunPatchCommandOptions = {
|
|
18
|
+
interactiveDecider?: (ctx: InteractiveContext) => Promise<InteractiveChoice>;
|
|
19
|
+
};
|
|
20
|
+
export declare function runPatchCommand(patchInput: string, scope: string | undefined, flags: PatchCommandFlags, options?: RunPatchCommandOptions): Promise<SpatchResult>;
|
|
21
|
+
type FormatPatchOutputOptions = {
|
|
22
|
+
color?: boolean;
|
|
23
|
+
chalkInstance?: ChalkInstance;
|
|
24
|
+
};
|
|
25
|
+
export declare function formatPatchOutput(result: SpatchResult, options?: FormatPatchOutputOptions): string;
|
|
26
|
+
export declare const patchCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
|
|
27
|
+
export {};
|