-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tools/spxls): implement diagnostic features and improve VFS
Signed-off-by: Aofei Sheng <aofei@aofeisheng.com>
- Loading branch information
Showing
16 changed files
with
8,816 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package internal | ||
|
||
//go:generate qexp -outdir pkg github.com/goplus/spx | ||
//go:generate qexp -outdir pkg github.com/hajimehoshi/ebiten/v2 | ||
|
||
import ( | ||
"fmt" | ||
|
||
_ "github.com/goplus/builder/tools/spxls/internal/pkg/github.com/goplus/spx" | ||
_ "github.com/goplus/builder/tools/spxls/internal/pkg/github.com/hajimehoshi/ebiten/v2" | ||
"github.com/goplus/igop" | ||
"github.com/goplus/igop/gopbuild" | ||
) | ||
|
||
var IGopContext = igop.NewContext(0) | ||
|
||
func init() { | ||
gopbuild.RegisterClassFileType(".spx", "Game", []*gopbuild.Class{{Ext: ".spx", Class: "SpriteImpl"}}, "github.com/goplus/spx") | ||
if err := gopbuild.RegisterPackagePatch(IGopContext, "github.com/goplus/spx", ` | ||
package spx | ||
import ( | ||
. "github.com/goplus/spx" | ||
) | ||
func Gopt_Game_Gopx_GetWidget[T any](sg ShapeGetter, name string) *T { | ||
widget := GetWidget_(sg, name) | ||
if result, ok := widget.(interface{}).(*T); ok { | ||
return result | ||
} else { | ||
panic("GetWidget: type mismatch") | ||
} | ||
} | ||
`); err != nil { | ||
panic(fmt.Errorf("register package patch failed: %w", err)) | ||
} | ||
} |
269 changes: 269 additions & 0 deletions
269
tools/spxls/internal/pkg/github.com/goplus/spx/export.go
Large diffs are not rendered by default.
Oops, something went wrong.
519 changes: 519 additions & 0 deletions
519
tools/spxls/internal/pkg/github.com/hajimehoshi/ebiten/v2/export.go
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package server | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"go/types" | ||
|
||
"github.com/goplus/builder/tools/spxls/internal" | ||
"github.com/goplus/builder/tools/spxls/internal/vfs" | ||
"github.com/goplus/gogen" | ||
gopast "github.com/goplus/gop/ast" | ||
gopparser "github.com/goplus/gop/parser" | ||
gopscanner "github.com/goplus/gop/scanner" | ||
goptoken "github.com/goplus/gop/token" | ||
goptypesutil "github.com/goplus/gop/x/typesutil" | ||
"github.com/goplus/igop/gopbuild" | ||
"github.com/goplus/mod/gopmod" | ||
gopmodload "github.com/goplus/mod/modload" | ||
) | ||
|
||
// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification#textDocument_diagnostic | ||
func (s *Server) textDocumentDiagnostic(params *DocumentDiagnosticParams) (*DocumentDiagnosticReport, error) { | ||
diags, err := s.diagnose() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &DocumentDiagnosticReport{Value: &RelatedFullDocumentDiagnosticReport{ | ||
FullDocumentDiagnosticReport: FullDocumentDiagnosticReport{ | ||
Kind: string(DiagnosticFull), | ||
Items: diags[string(params.TextDocument.URI)], | ||
}, | ||
}}, nil | ||
} | ||
|
||
// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification#workspace_diagnostic | ||
func (s *Server) workspaceDiagnostic(params *WorkspaceDiagnosticParams) (*WorkspaceDiagnosticReport, error) { | ||
diags, err := s.diagnose() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var items []WorkspaceDocumentDiagnosticReport | ||
for file, fileDiags := range diags { | ||
items = append(items, Or_WorkspaceDocumentDiagnosticReport{ | ||
Value: &WorkspaceFullDocumentDiagnosticReport{ | ||
URI: DocumentURI(file), | ||
FullDocumentDiagnosticReport: FullDocumentDiagnosticReport{ | ||
Kind: string(DiagnosticFull), | ||
Items: fileDiags, | ||
}, | ||
}, | ||
}) | ||
} | ||
return &WorkspaceDiagnosticReport{Items: items}, nil | ||
} | ||
|
||
// diagnose performs diagnostic checks for spx source files and returns diagnostics for each file. | ||
func (s *Server) diagnose() (map[string][]Diagnostic, error) { | ||
spxFiles, err := spxFilesFromFS(s.rootFS) | ||
if err != nil { | ||
return nil, fmt.Errorf("get spx files failed: %w", err) | ||
} | ||
|
||
diags := make(map[string][]Diagnostic, len(spxFiles)) | ||
|
||
fset := goptoken.NewFileSet() | ||
gpfs := vfs.NewGopParserFS(s.rootFS) | ||
mainPkgFiles := make(map[string]*gopast.File) | ||
for _, file := range spxFiles { | ||
diags[file] = nil | ||
|
||
f, err := gopparser.ParseFSEntry(fset, gpfs, file, nil, gopparser.Config{ | ||
Mode: gopparser.AllErrors | gopparser.ParseComments, | ||
}) | ||
if err != nil { | ||
// Handle parse errors. | ||
var parseErr gopscanner.ErrorList | ||
if errors.As(err, &parseErr) { | ||
for _, e := range parseErr { | ||
diags[e.Pos.Filename] = append(diags[e.Pos.Filename], Diagnostic{ | ||
Range: Range{ | ||
Start: FromGopTokenPosition(e.Pos), | ||
End: FromGopTokenPosition(e.Pos), | ||
}, | ||
Message: e.Msg, | ||
}) | ||
} | ||
continue | ||
} | ||
|
||
// Handle code generation errors. | ||
var codeErr *gogen.CodeError | ||
if errors.As(err, &codeErr) { | ||
position := codeErr.Fset.Position(codeErr.Pos) | ||
diags[position.Filename] = append(diags[position.Filename], Diagnostic{ | ||
Range: Range{ | ||
Start: FromGopTokenPosition(position), | ||
End: FromGopTokenPosition(position), | ||
}, | ||
Message: codeErr.Error(), | ||
}) | ||
continue | ||
} | ||
|
||
// Handle unknown errors. | ||
diags[file] = append(diags[file], Diagnostic{ | ||
Severity: SeverityError, | ||
Range: Range{ | ||
Start: Position{Line: 0, Character: 0}, | ||
End: Position{Line: 0, Character: 0}, | ||
}, | ||
Message: fmt.Sprintf("failed to parse spx file: %v", err), | ||
}) | ||
continue | ||
} | ||
if f.Name.Name == "main" { | ||
mainPkgFiles[file] = f | ||
} | ||
} | ||
if len(mainPkgFiles) == 0 { | ||
return nil, errors.New("no valid spx files found in main package") | ||
} | ||
|
||
mod := gopmod.New(gopmodload.Default) | ||
if err := mod.ImportClasses(); err != nil { | ||
return nil, fmt.Errorf("import classes failed: %w", err) | ||
} | ||
|
||
typeInfo := &goptypesutil.Info{ | ||
Types: make(map[gopast.Expr]types.TypeAndValue), | ||
Defs: make(map[*gopast.Ident]types.Object), | ||
Uses: make(map[*gopast.Ident]types.Object), | ||
Implicits: make(map[gopast.Node]types.Object), | ||
Selections: make(map[*gopast.SelectorExpr]*types.Selection), | ||
Scopes: make(map[gopast.Node]*types.Scope), | ||
} | ||
if err := goptypesutil.NewChecker( | ||
&types.Config{ | ||
Error: func(err error) { | ||
if typeErr, ok := err.(types.Error); ok { | ||
position := typeErr.Fset.Position(typeErr.Pos) | ||
file := position.Filename | ||
diags[file] = append(diags[file], Diagnostic{ | ||
Range: Range{ | ||
Start: FromGopTokenPosition(position), | ||
End: FromGopTokenPosition(position), | ||
}, | ||
Message: typeErr.Msg, | ||
Severity: SeverityError, | ||
}) | ||
} | ||
}, | ||
Importer: gopbuild.NewContext(internal.IGopContext), | ||
}, | ||
&goptypesutil.Config{ | ||
Types: types.NewPackage("main", "main"), | ||
Fset: fset, | ||
Mod: mod, | ||
}, | ||
nil, | ||
typeInfo, | ||
).Files(nil, gopASTFileMapToSlice(mainPkgFiles)); err != nil { | ||
// Errors should be handled by the type checker. | ||
} | ||
|
||
// TODO: spx resource reference check. | ||
|
||
return diags, nil | ||
} |
Oops, something went wrong.