Skip to content

Commit

Permalink
fix(tools/spxls): support array resource references and handle EOF
Browse files Browse the repository at this point in the history
  • Loading branch information
aofei committed Jan 16, 2025
1 parent 614c4fe commit 169fae2
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 34 deletions.
2 changes: 0 additions & 2 deletions tools/spxls/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ github.com/goplus/gop v1.2.0-pre.1.0.20250112163018-5fb12b1b2972 h1:wA4+I+DDfoNw
github.com/goplus/gop v1.2.0-pre.1.0.20250112163018-5fb12b1b2972/go.mod h1:lcW75c0a5v361jId1Vxs4lRrsasWsQDH0k3dG3Z7wH0=
github.com/goplus/mod v0.13.15 h1:IyneSjwm1VpwvHGz6hSHnFxZCuO6ULHcu74IAZcW9nw=
github.com/goplus/mod v0.13.15/go.mod h1:invR72Rz2+qpOOsXqxz830MX8/aR2GDR2EAow/WgfHI=
github.com/goplus/spx v1.1.0 h1:rke7F7YpbjeI583HkuNiUGmsbYdrloNT1ABYLfgv9i8=
github.com/goplus/spx v1.1.0/go.mod h1:1hkX9vPdDHrsA6LG8itiLMp83KIDJ0KlJI9CmpCoiCA=
github.com/goplus/spx v1.1.1-0.20241231062359-381fc67db3e1 h1:JpAWB4m+LYjEIZwKMVvIeKqqvIjbcLYTXkkvU92Vbkk=
github.com/goplus/spx v1.1.1-0.20241231062359-381fc67db3e1/go.mod h1:1hkX9vPdDHrsA6LG8itiLMp83KIDJ0KlJI9CmpCoiCA=
github.com/hajimehoshi/ebiten/v2 v2.7.9 h1:DYH/usAa9dMHcGkBIIEApJsVqDekrJBxYHmsBuly8Iw=
Expand Down
6 changes: 1 addition & 5 deletions tools/spxls/internal/server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,7 @@ func (s *Server) spxGetDefinitions(params []SpxGetDefinitionsParams) ([]SpxDefin

// Find the innermost scope contains the position.
tokenFile := result.fset.File(astFile.Pos())
// Gop compiler may truncate the last empty line of the file, so we need to adjust the line number to avoid panic.
// TODO: Check if this should be fixed by gop compiler.
line := min(int(param.Position.Line)+1, tokenFile.LineCount())
lineStart := tokenFile.LineStart(line)
pos := tokenFile.Pos(tokenFile.Offset(lineStart) + int(param.Position.Character))
pos := posAt(tokenFile, param.Position)
if !pos.IsValid() {
return nil, nil
}
Expand Down
54 changes: 47 additions & 7 deletions tools/spxls/internal/server/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,35 +198,75 @@ onStart => {
"assets/sprites/MySprite/index.json": []byte(`{}`),
}), nil)

mainSpxFileScopeParams := []SpxGetDefinitionsParams{
mySpriteSpxFileScopeParams := []SpxGetDefinitionsParams{
{
TextDocumentPositionParams: TextDocumentPositionParams{
TextDocument: TextDocumentIdentifier{URI: "file:///MySprite.spx"},
Position: Position{Line: 5, Character: 0},
},
},
}
mainSpxFileScopeDefs, err := s.spxGetDefinitions(mainSpxFileScopeParams)
mySpriteSpxFileScopeDefs, err := s.spxGetDefinitions(mySpriteSpxFileScopeParams)
require.NoError(t, err)
require.NotNil(t, mainSpxFileScopeDefs)
assert.True(t, spxDefinitionIdentifierSliceContains(mainSpxFileScopeDefs, SpxDefinitionIdentifier{
require.NotNil(t, mySpriteSpxFileScopeDefs)
assert.True(t, spxDefinitionIdentifierSliceContains(mySpriteSpxFileScopeDefs, SpxDefinitionIdentifier{
Package: util.ToPtr(spxPkgPath),
Name: util.ToPtr("Game.play"),
OverloadID: util.ToPtr("1"),
}))
assert.False(t, spxDefinitionIdentifierSliceContains(mainSpxFileScopeDefs, SpxDefinitionIdentifier{
assert.False(t, spxDefinitionIdentifierSliceContains(mySpriteSpxFileScopeDefs, SpxDefinitionIdentifier{
Package: util.ToPtr(spxPkgPath),
Name: util.ToPtr("Game.onStart"),
}))
assert.True(t, spxDefinitionIdentifierSliceContains(mainSpxFileScopeDefs, SpxDefinitionIdentifier{
assert.True(t, spxDefinitionIdentifierSliceContains(mySpriteSpxFileScopeDefs, SpxDefinitionIdentifier{
Package: util.ToPtr(spxPkgPath),
Name: util.ToPtr("Sprite.onStart"),
}))
assert.True(t, spxDefinitionIdentifierSliceContains(mainSpxFileScopeDefs, SpxDefinitionIdentifier{
assert.True(t, spxDefinitionIdentifierSliceContains(mySpriteSpxFileScopeDefs, SpxDefinitionIdentifier{
Package: util.ToPtr(spxPkgPath),
Name: util.ToPtr("Sprite.onClick"),
}))
})

t.Run("EOF", func(t *testing.T) {
s := New(newMapFSWithoutModTime(map[string][]byte{
"main.spx": []byte(`
onStart => {}
`),
}), nil)

mainSpxOnStartScopeParams := []SpxGetDefinitionsParams{
{
TextDocumentPositionParams: TextDocumentPositionParams{
TextDocument: TextDocumentIdentifier{URI: "file:///main.spx"},
Position: Position{Line: 1, Character: 0},
},
},
}
mainSpxOnStartScopeDefs, err := s.spxGetDefinitions(mainSpxOnStartScopeParams)
require.NoError(t, err)
require.NotNil(t, mainSpxOnStartScopeDefs)
assert.False(t, spxDefinitionIdentifierSliceContains(mainSpxOnStartScopeDefs, SpxDefinitionIdentifier{
Package: util.ToPtr(spxPkgPath),
Name: util.ToPtr("Game.onStart"),
}))

mainSpxFileScopeParams := []SpxGetDefinitionsParams{
{
TextDocumentPositionParams: TextDocumentPositionParams{
TextDocument: TextDocumentIdentifier{URI: "file:///main.spx"},
Position: Position{Line: 2, Character: 0},
},
},
}
mainSpxFileScopeDefs, err := s.spxGetDefinitions(mainSpxFileScopeParams)
require.NoError(t, err)
require.NotNil(t, mainSpxFileScopeDefs)
assert.True(t, spxDefinitionIdentifierSliceContains(mainSpxFileScopeDefs, SpxDefinitionIdentifier{
Package: util.ToPtr(spxPkgPath),
Name: util.ToPtr("Game.onStart"),
}))
})
}

// spxDefinitionIdentifierSliceContains reports whether a slice of [SpxDefinitionIdentifier]
Expand Down
52 changes: 36 additions & 16 deletions tools/spxls/internal/server/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,23 +918,20 @@ func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
// Use the last parameter type for variadic functions.
paramType = lastParamType
}
switch paramType.String() {
case spxBackdropNameTypeFullName:
s.inspectSpxBackdropResourceRefAtExpr(result, arg, paramType)
case spxSpriteNameTypeFullName, spxSpriteTypeFullName:
s.inspectSpxSpriteResourceRefAtExpr(result, arg, paramType)
case spxSpriteCostumeNameTypeFullName:
if spxSpriteResource != nil {
s.inspectSpxSpriteCostumeResourceRefAtExpr(result, spxSpriteResource, arg, paramType)
}
case spxSpriteAnimationNameTypeFullName:
if spxSpriteResource != nil {
s.inspectSpxSpriteAnimationResourceRefAtExpr(result, spxSpriteResource, arg, paramType)

// Handle slice/array parameter types.
if sliceType, ok := paramType.(*types.Slice); ok {
paramType = unwrapPointerType(sliceType.Elem())
} else if arrayType, ok := paramType.(*types.Array); ok {
paramType = unwrapPointerType(arrayType.Elem())
}

if sliceLit, ok := arg.(*gopast.SliceLit); ok {
for _, elt := range sliceLit.Elts {
s.inspectSpxResourceRefForTypeAtExpr(result, elt, paramType, spxSpriteResource)
}
case spxSoundNameTypeFullName, spxSoundTypeFullName:
s.inspectSpxSoundResourceRefAtExpr(result, arg, paramType)
case spxWidgetNameTypeFullName:
s.inspectSpxWidgetResourceRefAtExpr(result, arg, paramType)
} else {
s.inspectSpxResourceRefForTypeAtExpr(result, arg, paramType, spxSpriteResource)
}
}
default:
Expand Down Expand Up @@ -998,6 +995,29 @@ func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
}
}

// inspectSpxResourceRefForTypeAtExpr inspects a spx resource reference for a
// given type at an expression.
func (s *Server) inspectSpxResourceRefForTypeAtExpr(result *compileResult, expr gopast.Expr, typ types.Type, spxSpriteResource *SpxSpriteResource) {
switch typ.String() {
case spxBackdropNameTypeFullName:
s.inspectSpxBackdropResourceRefAtExpr(result, expr, typ)
case spxSpriteNameTypeFullName, spxSpriteTypeFullName:
s.inspectSpxSpriteResourceRefAtExpr(result, expr, typ)
case spxSpriteCostumeNameTypeFullName:
if spxSpriteResource != nil {
s.inspectSpxSpriteCostumeResourceRefAtExpr(result, spxSpriteResource, expr, typ)
}
case spxSpriteAnimationNameTypeFullName:
if spxSpriteResource != nil {
s.inspectSpxSpriteAnimationResourceRefAtExpr(result, spxSpriteResource, expr, typ)
}
case spxSoundNameTypeFullName, spxSoundTypeFullName:
s.inspectSpxSoundResourceRefAtExpr(result, expr, typ)
case spxWidgetNameTypeFullName:
s.inspectSpxWidgetResourceRefAtExpr(result, expr, typ)
}
}

// inspectSpxBackdropResourceRefAtExpr inspects a spx backdrop resource
// reference at an expression. It returns the spx backdrop resource if it was
// successfully retrieved.
Expand Down
4 changes: 1 addition & 3 deletions tools/spxls/internal/server/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ func (s *Server) textDocumentCompletion(params *CompletionParams) ([]CompletionI
}

tokenFile := result.fset.File(astFile.Pos())
line := min(int(params.Position.Line)+1, tokenFile.LineCount())
lineStart := tokenFile.LineStart(line)
pos := tokenFile.Pos(tokenFile.Offset(lineStart) + int(params.Position.Character))
pos := posAt(tokenFile, params.Position)
if !pos.IsValid() {
return nil, nil
}
Expand Down
20 changes: 20 additions & 0 deletions tools/spxls/internal/server/hover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ onStart => {
clone
imagePoint.X = 100
}
onTouchStart ["MySprite"], => {}
`),
"assets/index.json": []byte(`{}`),
"assets/sprites/MySprite/index.json": []byte(`{"costumes":[{"name":"costume1"}]}`),
Expand Down Expand Up @@ -442,6 +443,25 @@ onStart => {
End: Position{Line: 6, Character: 13},
},
}, imagePointFieldHover)

onTouchStartFirstArgHover, err := s.textDocumentHover(&HoverParams{
TextDocumentPositionParams: TextDocumentPositionParams{
TextDocument: TextDocumentIdentifier{URI: "file:///MySprite.spx"},
Position: Position{Line: 8, Character: 14},
},
})
require.NoError(t, err)
require.NotNil(t, onTouchStartFirstArgHover)
assert.Equal(t, &Hover{
Contents: MarkupContent{
Kind: Markdown,
Value: "<resource-preview resource=\"spx://resources/sprites/MySprite\" />\n",
},
Range: Range{
Start: Position{Line: 8, Character: 14},
End: Position{Line: 8, Character: 24},
},
}, onTouchStartFirstArgHover)
})

t.Run("InvalidPosition", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion tools/spxls/internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type MessageReplier interface {
ReplyMessage(m jsonrpc2.Message) error
}

// Server is the core language server implementation that handles LSP messages
// Server is the core language server implementation that handles LSP messages.
type Server struct {
workspaceRootURI DocumentURI
workspaceRootFS *vfs.MapFS
Expand Down
9 changes: 9 additions & 0 deletions tools/spxls/internal/server/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ func getStringLitOrConstValue(expr gopast.Expr, tv types.TypeAndValue) (string,
}
}

// posAt returns the [goptoken.Pos] of the given position in the given token file.
func posAt(tokenFile *goptoken.File, position Position) goptoken.Pos {
line := int(position.Line) + 1
if line > tokenFile.LineCount() {
return goptoken.Pos(tokenFile.Base() + tokenFile.Size()) // EOF
}
return tokenFile.LineStart(line) + goptoken.Pos(int(position.Character))
}

// deduplicateLocations deduplicates locations.
func deduplicateLocations(locations []Location) []Location {
result := make([]Location, 0, len(locations))
Expand Down

0 comments on commit 169fae2

Please sign in to comment.