Skip to content

Commit

Permalink
feat(tools/spxls): implement diagnostic features and improve VFS
Browse files Browse the repository at this point in the history
Signed-off-by: Aofei Sheng <aofei@aofeisheng.com>
  • Loading branch information
aofei committed Nov 25, 2024
1 parent 4dd56e1 commit b55c8e3
Show file tree
Hide file tree
Showing 16 changed files with 8,816 additions and 90 deletions.
35 changes: 34 additions & 1 deletion tools/spxls/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,47 @@ module github.com/goplus/builder/tools/spxls
go 1.21.0

require (
github.com/goplus/gogen v1.15.2
github.com/goplus/gop v1.2.6
github.com/goplus/igop v0.27.1
github.com/goplus/mod v0.13.10
github.com/goplus/spx v1.0.1-0.20241029011511-845f2c0e2e74
github.com/hajimehoshi/ebiten/v2 v2.8.0-alpha.3
github.com/stretchr/testify v1.9.0
)

require (
github.com/ajstarks/svgo v0.0.0-20210927141636-6d70534b1098 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/goplus/gogen v1.15.2 // indirect
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
github.com/ebitengine/hideconsole v1.0.0 // indirect
github.com/ebitengine/oto/v3 v3.3.0-alpha.3 // indirect
github.com/ebitengine/purego v0.8.0-alpha.3 // indirect
github.com/esimov/stackblur-go v1.0.1-0.20190121110005-00e727e3c7a9 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/goplus/canvas v0.1.0 // indirect
github.com/goplus/reflectx v1.2.2 // indirect
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
github.com/jezek/xgb v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/qiniu/audio v0.2.1 // indirect
github.com/qiniu/x v1.13.10 // indirect
github.com/srwiley/oksvg v0.0.0-20210519022825-9fc0c575d5fe // indirect
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
github.com/timandy/routine v1.1.1 // indirect
github.com/visualfc/funcval v0.1.4 // indirect
github.com/visualfc/gid v0.1.0 // indirect
github.com/visualfc/goembed v0.3.2 // indirect
github.com/visualfc/xtype v0.2.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.23.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
189 changes: 189 additions & 0 deletions tools/spxls/go.sum

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions tools/spxls/internal/internal.go
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 tools/spxls/internal/pkg/github.com/goplus/spx/export.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

170 changes: 170 additions & 0 deletions tools/spxls/internal/server/diagnostic.go
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.

Check warning on line 167 in tools/spxls/internal/server/diagnostic.go

View check run for this annotation

qiniu-x / note-check

tools/spxls/internal/server/diagnostic.go#L167

A Note is recommended to use "MARKER(uid): note body" format.

return diags, nil
}
Loading

0 comments on commit b55c8e3

Please sign in to comment.