Skip to content

Commit e0d7028

Browse files
committed
fix: reset to v0.6.4 & add skeleton api & lock fix
1 parent 05ab675 commit e0d7028

File tree

5 files changed

+241
-9
lines changed

5 files changed

+241
-9
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ tmp/codegraph/*.profile
1717
.costrict
1818
.coignore
1919
.documents
20-
.cospec
20+
.cospec
21+
internal/service/*.profile

internal/dto/backend.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// internal/dto/backend.go - 后端API请求和响应数据结构定义
22
package dto
33

4-
import "codebase-indexer/pkg/codegraph/types"
4+
import (
5+
"codebase-indexer/pkg/codegraph/types"
6+
)
57

68
// SearchReferenceRequest 关系检索请求
79
type SearchReferenceRequest struct {
@@ -205,6 +207,45 @@ func ToPosition(ranges []int32) Position {
205207

206208
}
207209

210+
// GetFileSkeletonRequest 获取文件骨架请求
211+
type GetFileSkeletonRequest struct {
212+
ClientId string `form:"clientId" binding:"required"`
213+
WorkspacePath string `form:"workspacePath" binding:"required"`
214+
FilePath string `form:"filePath" binding:"required"`
215+
FilteredBy string `form:"filteredBy,omitempty"` // 可选: definition | reference
216+
}
217+
218+
// FileSkeletonData 文件骨架响应数据
219+
type FileSkeletonData struct {
220+
Path string `json:"path"`
221+
Language string `json:"language"`
222+
Timestamp int64 `json:"timestamp"`
223+
Imports []*FileSkeletonImport `json:"imports,omitempty"`
224+
Package *FileSkeletonPackage `json:"package,omitempty"`
225+
Elements []*FileSkeletonElement `json:"elements"`
226+
}
227+
228+
// FileSkeletonImport 导入信息(还原后的原始内容)
229+
type FileSkeletonImport struct {
230+
Content string `json:"content"` // 原始导入语句
231+
Range []int `json:"range"` // [startLine, startCol, endLine, endCol] - 从1开始
232+
}
233+
234+
// FileSkeletonPackage 包信息
235+
type FileSkeletonPackage struct {
236+
Name string `json:"name"`
237+
Range []int `json:"range"` // 从1开始
238+
}
239+
240+
// FileSkeletonElement 元素信息
241+
type FileSkeletonElement struct {
242+
Name string `json:"name"`
243+
Signature string `json:"signature"` // 通过行号读取的签名,限制200字符
244+
IsDefinition bool `json:"isDefinition"` // 默认为 false
245+
ElementType string `json:"elementType"` // 类型名称字符串(如 "FUNCTION")
246+
Range []int `json:"range"` // [startLine, startCol, endLine, endCol] - 从1开始
247+
}
248+
208249
const (
209250
Embedding = "embedding"
210251
Codegraph = "codegraph"

internal/service/codebase.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const definitionFillContentNodeLimit = 20
7272
const definitionFillContentLineLimit = 200
7373
const DefaultMaxCodeSnippetLines = 500
7474
const DefaultMaxCodeSnippets = 200
75+
const maxSignatureLength = 200
7576

7677
// NewCodebaseService 创建新的代码库服务
7778
func NewCodebaseService(
@@ -631,6 +632,42 @@ func (l *codebaseService) DeleteIndex(ctx context.Context, req *dto.DeleteIndexR
631632
return nil
632633
}
633634

635+
func (s *codebaseService) GetFileSkeleton(ctx context.Context, req *dto.GetFileSkeletonRequest) (*dto.FileSkeletonData, error) {
636+
// 1. 参数校验
637+
if req.WorkspacePath == "" || req.FilePath == "" {
638+
return nil, errs.NewMissingParamError("workspacePath or filePath")
639+
}
640+
641+
// 2. 路径处理(相对/绝对)
642+
filePath := req.FilePath
643+
if !filepath.IsAbs(filePath) {
644+
filePath = filepath.Join(req.WorkspacePath, filePath)
645+
}
646+
647+
// 验证路径是否在 workspace 内
648+
if err := s.checkPath(ctx, req.WorkspacePath, []string{filePath}); err != nil {
649+
return nil, err
650+
}
651+
652+
// 3. 获取原始 FileElementTable
653+
table, err := s.indexer.GetFileElementTable(ctx, req.WorkspacePath, filePath)
654+
if err != nil {
655+
return nil, fmt.Errorf("failed to get file element table: %w", err)
656+
}
657+
658+
// 4. 读取文件内容(用于提取签名和还原imports)
659+
fileContent, err := s.workspaceReader.ReadFile(ctx, filePath, types.ReadOptions{})
660+
if err != nil {
661+
s.logger.Warn("failed to read file content for %s: %v", filePath, err)
662+
fileContent = nil // 继续处理,但签名和imports还原会失败
663+
}
664+
665+
// 5. 转换数据结构
666+
result := convertToFileSkeletonData(table, fileContent, req.FilteredBy)
667+
668+
return result, nil
669+
}
670+
634671
func convertStatus(status int) string {
635672
var indexStatus string
636673
switch status {
@@ -645,3 +682,131 @@ func convertStatus(status int) string {
645682
}
646683
return indexStatus
647684
}
685+
686+
// convertToFileSkeletonData 转换 FileElementTable 到 FileSkeletonData
687+
func convertToFileSkeletonData(
688+
table *codegraphpb.FileElementTable,
689+
fileContent []byte,
690+
filteredBy string,
691+
) *dto.FileSkeletonData {
692+
// 按行分割文件内容(KISS原则)
693+
var lines []string
694+
if fileContent != nil {
695+
lines = strings.Split(string(fileContent), "\n")
696+
}
697+
698+
// 转换 imports
699+
imports := make([]*dto.FileSkeletonImport, 0, len(table.Imports))
700+
for _, imp := range table.Imports {
701+
content := restoreImportContent(lines, imp.Range)
702+
imports = append(imports, &dto.FileSkeletonImport{
703+
Content: content,
704+
Range: convertRange(imp.Range),
705+
})
706+
}
707+
708+
// 转换 package
709+
var pkg *dto.FileSkeletonPackage
710+
if table.Package != nil {
711+
pkg = &dto.FileSkeletonPackage{
712+
Name: table.Package.Name,
713+
Range: convertRange(table.Package.Range),
714+
}
715+
}
716+
717+
// 转换和过滤 elements
718+
elements := make([]*dto.FileSkeletonElement, 0, len(table.Elements))
719+
for _, elem := range table.Elements {
720+
// 根据 filteredBy 参数过滤
721+
if filteredBy == "definition" && !elem.IsDefinition {
722+
continue
723+
}
724+
if filteredBy == "reference" && elem.IsDefinition {
725+
continue
726+
}
727+
728+
// 提取签名
729+
signature := extractSignature(lines, elem.Range, maxSignatureLength)
730+
if signature == "" {
731+
signature = elem.Name // 如果为空则使用 name
732+
}
733+
734+
elements = append(elements, &dto.FileSkeletonElement{
735+
Name: elem.Name,
736+
Signature: signature,
737+
IsDefinition: elem.IsDefinition,
738+
ElementType: convertElementType(elem.ElementType),
739+
Range: convertRange(elem.Range),
740+
})
741+
}
742+
743+
return &dto.FileSkeletonData{
744+
Path: table.Path,
745+
Language: table.Language,
746+
Timestamp: table.Timestamp,
747+
Imports: imports,
748+
Package: pkg,
749+
Elements: elements,
750+
}
751+
}
752+
753+
// extractSignature 提取签名(从指定行读取,限制长度)
754+
func extractSignature(lines []string, ranges []int32, maxLength int) string {
755+
if len(ranges) < 1 || lines == nil || len(lines) == 0 {
756+
return ""
757+
}
758+
lineNumber := int(ranges[0]) // 0-based
759+
if lineNumber < 0 || lineNumber >= len(lines) {
760+
return ""
761+
}
762+
line := strings.TrimSpace(lines[lineNumber])
763+
if len(line) > maxLength {
764+
return line[:maxLength]
765+
}
766+
return line
767+
}
768+
769+
// restoreImportContent 还原 import 的原始内容
770+
func restoreImportContent(lines []string, ranges []int32) string {
771+
if len(ranges) < 3 || lines == nil {
772+
return ""
773+
}
774+
startLine := int(ranges[0]) // 0-based
775+
endLine := int(ranges[2]) // 0-based
776+
777+
if startLine < 0 || endLine >= len(lines) || startLine > endLine {
778+
return ""
779+
}
780+
781+
// 单行import
782+
if startLine == endLine {
783+
return strings.TrimSpace(lines[startLine])
784+
}
785+
786+
// 多行import(合并为一行)
787+
var builder strings.Builder
788+
for i := startLine; i <= endLine && i < len(lines); i++ {
789+
trimmed := strings.TrimSpace(lines[i])
790+
if trimmed != "" {
791+
builder.WriteString(trimmed)
792+
if i < endLine {
793+
builder.WriteString(" ")
794+
}
795+
}
796+
}
797+
return builder.String()
798+
}
799+
800+
// convertElementType 转换 ElementType 枚举到字符串名称
801+
func convertElementType(et codegraphpb.ElementType) string {
802+
return codegraphpb.ElementType_name[int32(et)]
803+
}
804+
805+
// convertRange 转换 range(+1 转换:0-based -> 1-based)
806+
func convertRange(protoRange []int32) []int {
807+
result := make([]int, len(protoRange))
808+
for i, v := range protoRange {
809+
result[i] = int(v) + 1
810+
}
811+
return result
812+
}

internal/service/indexer_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -778,11 +778,11 @@ func TestIndexer_QueryCallGraph_BySymbolName(t *testing.T) {
778778
// IncludeExts: []string{".go"},
779779
// },
780780
{
781-
name: "buildCallGraphBFS方法调用链",
781+
name: "setupTestEnvironment方法调用链",
782782
filePath: "internal/service/indexer_test.go",
783-
symbolName: "buildCallGraphBFS",
783+
symbolName: "setupTestEnvironment",
784784
maxLayer: 20,
785-
desc: "查询buildCallGraphBFS方法的调用链",
785+
desc: "查询setupTestEnvironment方法的调用链",
786786
project: "codebase-indexer",
787787
workspaceDir: "/home/kcx/codeWorkspace/codebase-indexer",
788788
IncludeExts: []string{".go"},
@@ -895,7 +895,9 @@ func TestIndexer_QueryCallGraph_BySymbolName(t *testing.T) {
895895
env := setupTestEnvironment(t)
896896
defer teardownTestEnvironment(t, env, nil)
897897

898-
env.workspaceDir = tc.workspaceDir
898+
if tc.workspaceDir != "" {
899+
env.workspaceDir = tc.workspaceDir
900+
}
899901
testVisitPattern.IncludeExts = tc.IncludeExts
900902

901903
err := initWorkspaceModel(env)
@@ -1006,7 +1008,9 @@ func TestIndexer_QueryCallGraph_ByLineRange(t *testing.T) {
10061008
env := setupTestEnvironment(t)
10071009
defer teardownTestEnvironment(t, env, nil)
10081010

1009-
env.workspaceDir = tc.workspaceDir
1011+
if tc.workspaceDir != "" {
1012+
env.workspaceDir = tc.workspaceDir
1013+
}
10101014
testVisitPattern.IncludeExts = tc.IncludeExts
10111015

10121016
err := initWorkspaceModel(env)
@@ -1026,12 +1030,13 @@ func TestIndexer_QueryCallGraph_ByLineRange(t *testing.T) {
10261030
assert.NoError(t, err)
10271031
indexEnd := time.Now()
10281032

1029-
// 构建完整文件路径
10301033
start := time.Now()
1034+
// 构建完整文件路径
1035+
fullPath := filepath.Join(env.workspaceDir, tc.filePath)
10311036
// 查询调用链
10321037
opts := &types.QueryCallGraphOptions{
10331038
Workspace: env.workspaceDir,
1034-
FilePath: tc.filePath,
1039+
FilePath: fullPath,
10351040
LineRange: fmt.Sprintf("%d-%d", tc.startLine, tc.endLine),
10361041
MaxLayer: tc.maxLayer,
10371042
}

pkg/codegraph/resolver/javascript.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ var jsReplacer = strings.NewReplacer(
2222
")", "",
2323
)
2424

25+
// JavaScript 保留关键字集合
26+
var jsReservedKeywords = map[string]bool{
27+
"if": true, "else": true, "for": true, "while": true, "do": true,
28+
"switch": true, "case": true, "default": true,
29+
"try": true, "catch": true, "finally": true,
30+
"break": true, "continue": true, "return": true,
31+
"throw": true, "with": true, "yield": true, "await": true,
32+
}
33+
2534
type JavaScriptResolver struct {
2635
}
2736

@@ -105,6 +114,17 @@ func (js *JavaScriptResolver) resolveFunction(ctx context.Context, element *Func
105114
}
106115

107116
func (js *JavaScriptResolver) resolveMethod(ctx context.Context, element *Method, rc *ResolveContext) ([]Element, error) {
117+
// 验证:拒绝保留关键字作为方法名
118+
for _, capture := range rc.Match.Captures {
119+
nodeCaptureName := rc.CaptureNames[capture.Index]
120+
if types.ToElementType(nodeCaptureName) == types.ElementTypeMethodName {
121+
methodName := capture.Node.Utf8Text(rc.SourceFile.Content)
122+
if jsReservedKeywords[methodName] {
123+
return []Element{}, nil
124+
}
125+
}
126+
}
127+
108128
elements := []Element{element}
109129
rootCap := rc.Match.Captures[0]
110130
for _, capture := range rc.Match.Captures {

0 commit comments

Comments
 (0)