Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions cmd/mxcli/cmd_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,9 @@ Examples:
}

// Create lint context
ctx := linter.NewLintContext(cat)
ctx := linter.NewLintContext(cat, exec.Backend())
ctx.SetExcludedModules(excludeModules)

// Set reader so rules that inspect raw BSON (MPR004, MPR005) work
if reader := exec.Reader(); reader != nil {
ctx.SetReader(reader)
}

// Create linter and register rules
lint := linter.New(ctx)
lint.AddRule(rules.NewNamingConventionRule())
Expand Down
7 changes: 1 addition & 6 deletions cmd/mxcli/cmd_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,9 @@ Examples:
}

// Create lint context
ctx := linter.NewLintContext(cat)
ctx := linter.NewLintContext(cat, exec.Backend())
ctx.SetExcludedModules(excludeModules)

// Set reader so rules that inspect raw BSON work
if reader := exec.Reader(); reader != nil {
ctx.SetReader(reader)
}

// Create linter and register all rules
lint := linter.New(ctx)

Expand Down
4 changes: 3 additions & 1 deletion mdl/backend/mpr/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package mprbackend

import (
"github.com/mendixlabs/mxcli/mdl/backend"
"github.com/mendixlabs/mxcli/mdl/linter"
"github.com/mendixlabs/mxcli/mdl/types"
"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/agenteditor"
Expand All @@ -20,6 +21,7 @@ import (
)

var _ backend.FullBackend = (*MprBackend)(nil)
var _ linter.LintReader = (*MprBackend)(nil)

// MprBackend implements backend.FullBackend by delegating to mpr.Reader
// and mpr.Writer.
Expand Down Expand Up @@ -87,7 +89,7 @@ func (b *MprBackend) MprReader() *mpr.Reader { return b.reader }

func (b *MprBackend) Version() types.MPRVersion { return convertMPRVersion(b.reader.Version()) }
func (b *MprBackend) ProjectVersion() *types.ProjectVersion {
return convertProjectVersion(b.reader.ProjectVersion())
return b.reader.ProjectVersion()
}
func (b *MprBackend) GetMendixVersion() (string, error) { return b.reader.GetMendixVersion() }

Expand Down
16 changes: 0 additions & 16 deletions mdl/backend/mpr/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package mprbackend
import (
"github.com/mendixlabs/mxcli/mdl/types"
"github.com/mendixlabs/mxcli/sdk/mpr"
"github.com/mendixlabs/mxcli/sdk/mpr/version"
)

// ---------------------------------------------------------------------------
Expand All @@ -14,21 +13,6 @@ import (

func convertMPRVersion(v mpr.MPRVersion) types.MPRVersion { return types.MPRVersion(v) }

func convertProjectVersion(v *version.ProjectVersion) *types.ProjectVersion {
if v == nil {
return nil
}
return &types.ProjectVersion{
ProductVersion: v.ProductVersion,
BuildVersion: v.BuildVersion,
FormatVersion: v.FormatVersion,
SchemaHash: v.SchemaHash,
MajorVersion: v.MajorVersion,
MinorVersion: v.MinorVersion,
PatchVersion: v.PatchVersion,
}
}

func convertFolderInfoSlice(in []*mpr.FolderInfo, err error) ([]*types.FolderInfo, error) {
if err != nil || in == nil {
return nil, err
Expand Down
21 changes: 0 additions & 21 deletions mdl/backend/mpr/convert_roundtrip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/mendixlabs/mxcli/mdl/types"
"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/mpr"
"github.com/mendixlabs/mxcli/sdk/mpr/version"
"go.mongodb.org/mongo-driver/bson"
)

Expand All @@ -23,26 +22,6 @@ var errTest = errors.New("test error")
// Forward conversions: sdk/mpr -> mdl/types
// ---------------------------------------------------------------------------

func TestConvertProjectVersion(t *testing.T) {
in := &version.ProjectVersion{
ProductVersion: "10.18.0", BuildVersion: "1234",
FormatVersion: 42, SchemaHash: "abc123",
MajorVersion: 10, MinorVersion: 18, PatchVersion: 0,
}
out := convertProjectVersion(in)
if out.ProductVersion != "10.18.0" || out.BuildVersion != "1234" ||
out.FormatVersion != 42 || out.SchemaHash != "abc123" ||
out.MajorVersion != 10 || out.MinorVersion != 18 || out.PatchVersion != 0 {
t.Errorf("field mismatch: %+v", out)
}
}

func TestConvertProjectVersion_Nil(t *testing.T) {
if convertProjectVersion(nil) != nil {
t.Error("expected nil for nil input")
}
}

func TestConvertFolderInfoSlice(t *testing.T) {
in := []*mpr.FolderInfo{
{ID: model.ID("f1"), ContainerID: model.ID("c1"), Name: "Folder1"},
Expand Down
3 changes: 1 addition & 2 deletions mdl/executor/cmd_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ func execLint(ctx *ExecContext, s *ast.LintStmt) error {
}

// Create lint context
lintCtx := linter.NewLintContext(ctx.Catalog)
lintCtx.SetReader(ctx.Reader())
lintCtx := linter.NewLintContext(ctx.Catalog, ctx.Backend)

// Load configuration
projectDir := filepath.Dir(ctx.MprPath)
Expand Down
17 changes: 0 additions & 17 deletions mdl/executor/exec_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/mendixlabs/mxcli/mdl/catalog"
"github.com/mendixlabs/mxcli/mdl/diaglog"
"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/mpr"
sqllib "github.com/mendixlabs/mxcli/sql"
)

Expand Down Expand Up @@ -215,19 +214,3 @@ func (ctx *ExecContext) ensureSqlMgr() *sqllib.Manager {
}
return ctx.SqlMgr
}

// Reader returns the MPR reader, or nil if not connected.
// Deprecated: External callers should migrate to using Backend methods directly.
// TODO(shared-types): remove once all callers use Backend — target: v0.next milestone.
func (ctx *ExecContext) Reader() *mpr.Reader {
if ctx.Backend == nil {
return nil
}
type readerProvider interface {
MprReader() *mpr.Reader
}
if rp, ok := ctx.Backend.(readerProvider); ok {
return rp.MprReader()
}
return nil
}
25 changes: 8 additions & 17 deletions mdl/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/mendixlabs/mxcli/mdl/types"
"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/domainmodel"
"github.com/mendixlabs/mxcli/sdk/mpr"
sqllib "github.com/mendixlabs/mxcli/sql"
)

Expand Down Expand Up @@ -309,27 +308,19 @@ func (e *Executor) Catalog() *catalog.Catalog {
return c
}

// Reader returns the MPR reader, or nil if not connected.
// Deprecated: External callers should migrate to using Backend methods directly.
// TODO(shared-types): remove once all callers use Backend — target: v0.next milestone.
func (e *Executor) Reader() *mpr.Reader {
if e.backend == nil {
return nil
}
type readerProvider interface {
MprReader() *mpr.Reader
}
if rp, ok := e.backend.(readerProvider); ok {
return rp.MprReader()
}
return nil
}

// IsConnected returns true if connected to a project.
func (e *Executor) IsConnected() bool {
return e.backend != nil && e.backend.IsConnected()
}

// Backend returns the full backend, or nil if not connected.
func (e *Executor) Backend() backend.FullBackend {
if e.backend == nil || !e.backend.IsConnected() {
return nil
}
return e.backend
}

// Close closes the connection to the project and all SQL connections.
func (e *Executor) Close() error {
var closeErr error
Expand Down
8 changes: 4 additions & 4 deletions mdl/executor/roundtrip_doctype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
"testing"

"github.com/mendixlabs/mxcli/mdl/ast"
"github.com/mendixlabs/mxcli/mdl/types"
"github.com/mendixlabs/mxcli/mdl/visitor"
"github.com/mendixlabs/mxcli/sdk/mpr/version"
)

// scriptModuleDeps maps script filenames to marketplace module MPKs they require.
Expand Down Expand Up @@ -142,7 +142,7 @@ func TestMxCheck_DoctypeScripts(t *testing.T) {
}

// Filter out version-gated sections that don't match this project's Mendix version
pv := env.executor.Reader().ProjectVersion()
pv := env.executor.Backend().ProjectVersion()
filtered, skippedLines := filterByVersion(string(content), pv)
if skippedLines > 0 {
t.Logf("Mendix %s: skipped %d version-gated lines", pv.ProductVersion, skippedLines)
Expand Down Expand Up @@ -228,7 +228,7 @@ type versionConstraint struct {
}

// matches returns true if the project version satisfies this constraint.
func (vc *versionConstraint) matches(pv *version.ProjectVersion) bool {
func (vc *versionConstraint) matches(pv *types.ProjectVersion) bool {
if vc.minMajor >= 0 {
if !pv.IsAtLeast(vc.minMajor, vc.minMinor) {
return false
Expand Down Expand Up @@ -321,7 +321,7 @@ func parseMajorMinor(s string) (int, int, bool) {
// Sections are delimited by "-- @version: <constraint>" directives.
// A directive applies to all following lines until the next directive or end of file.
// "-- @version: any" resets to unconditional inclusion.
func filterByVersion(content string, pv *version.ProjectVersion) (string, int) {
func filterByVersion(content string, pv *types.ProjectVersion) (string, int) {
var result strings.Builder
var currentConstraint *versionConstraint // nil = no constraint (always include)
skippedLines := 0
Expand Down
2 changes: 1 addition & 1 deletion mdl/executor/roundtrip_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ func (e *testEnv) assertContains(createMDL string, expectedProps []string, opts
// requireMinVersion skips the test if the project's Mendix version is below the given minimum.
func (e *testEnv) requireMinVersion(t *testing.T, major, minor int) {
t.Helper()
pv := e.executor.Reader().ProjectVersion()
pv := e.executor.Backend().ProjectVersion()
if !pv.IsAtLeast(major, minor) {
t.Skipf("Requires Mendix %d.%d+ (project is %s)", major, minor, pv.ProductVersion)
}
Expand Down
35 changes: 24 additions & 11 deletions mdl/linter/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,46 @@ import (
"iter"

"github.com/mendixlabs/mxcli/mdl/catalog"
"github.com/mendixlabs/mxcli/sdk/mpr"
"github.com/mendixlabs/mxcli/mdl/types"
"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/microflows"
"github.com/mendixlabs/mxcli/sdk/pages"
"github.com/mendixlabs/mxcli/sdk/security"
)

// LintReader provides read access to MPR document data needed by lint rules.
// Implemented by MprBackend (and any backend satisfying these signatures).
type LintReader interface {
GetMicroflow(id model.ID) (*microflows.Microflow, error)
GetProjectSecurity() (*security.ProjectSecurity, error)
GetNavigation() (*types.NavigationDocument, error)
ListPages() ([]*pages.Page, error)
ListModules() ([]*model.Module, error)
ListFolders() ([]*types.FolderInfo, error)
GetRawUnit(id model.ID) (map[string]any, error)
}

// LintContext wraps a catalog and provides rule-friendly APIs.
type LintContext struct {
catalog *catalog.Catalog
db catalog.CatalogDB
excluded map[string]bool
reader *mpr.Reader
}

// SetReader sets the MPR reader for rules that need to inspect full document data.
func (ctx *LintContext) SetReader(reader *mpr.Reader) {
ctx.reader = reader
reader LintReader
}

// Reader returns the MPR reader, or nil if not set.
func (ctx *LintContext) Reader() *mpr.Reader {
// Reader returns the LintReader, or nil if not set.
func (ctx *LintContext) Reader() LintReader {
return ctx.reader
}

// NewLintContext creates a new LintContext from a catalog.
func NewLintContext(cat *catalog.Catalog) *LintContext {
// NewLintContext creates a new LintContext from a catalog and an optional reader.
// reader may be nil; rules that require backend access must check Reader() != nil.
func NewLintContext(cat *catalog.Catalog, reader LintReader) *LintContext {
return &LintContext{
catalog: cat,
db: cat.CatalogDB(),
excluded: make(map[string]bool),
reader: reader,
}
}

Expand Down
3 changes: 1 addition & 2 deletions mdl/linter/rules/page_navigation_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/mendixlabs/mxcli/mdl/linter"
"github.com/mendixlabs/mxcli/mdl/types"
"github.com/mendixlabs/mxcli/sdk/mpr"
)

// PageNavigationSecurityRule checks that pages used in navigation have at least
Expand Down Expand Up @@ -134,7 +133,7 @@ func collectMenuPages(items []*types.NavMenuItem, profileName string, navPages m
}

// buildPageRoleCountMap builds a map of qualified page name → number of allowed roles.
func buildPageRoleCountMap(reader *mpr.Reader) map[string]int {
func buildPageRoleCountMap(reader linter.LintReader) map[string]int {
result := make(map[string]int)

pages, err := reader.ListPages()
Expand Down
32 changes: 32 additions & 0 deletions mdl/types/infrastructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,38 @@ type ProjectVersion struct {
PatchVersion int
}

// IsAtLeast returns true if this version is at least the specified major.minor version.
func (v *ProjectVersion) IsAtLeast(major, minor int) bool {
if v.MajorVersion > major {
return true
}
return v.MajorVersion == major && v.MinorVersion >= minor
}

// IsAtLeastFull returns true if this version is at least the specified major.minor.patch version.
func (v *ProjectVersion) IsAtLeastFull(major, minor, patch int) bool {
if v.MajorVersion > major {
return true
}
if v.MajorVersion == major && v.MinorVersion > minor {
return true
}
if v.MajorVersion == major && v.MinorVersion == minor && v.PatchVersion >= patch {
return true
}
return false
}

// String returns the product version string.
func (v *ProjectVersion) String() string {
return v.ProductVersion
}

// IsMPRv2 returns true if the project uses MPR v2 format (mprcontents folder).
func (v *ProjectVersion) IsMPRv2() bool {
return v.FormatVersion >= 2
}

// FolderInfo is a lightweight folder descriptor.
type FolderInfo struct {
ID model.ID
Expand Down
Loading