Documentation
¶
Overview ¶
Package framework provides a unified framework definition system for test framework detection and parsing. It replaces the dual registry system (matchers + strategies) with a single unified Definition type.
Example (Definition) ¶
ExampleDefinition demonstrates how to create a complete framework definition.
package main
import (
"fmt"
"github.com/specvital/core/pkg/domain"
"github.com/specvital/core/pkg/parser/framework"
"github.com/specvital/core/pkg/parser/framework/matchers"
)
func main() {
// Create a framework definition for a hypothetical test framework
def := &framework.Definition{
Name: "mytest",
Languages: []domain.Language{
domain.LanguageTypeScript,
domain.LanguageJavaScript,
},
Matchers: []framework.Matcher{
// Match import statements
matchers.NewImportMatcher("mytest", "mytest/"),
// Match config files
matchers.NewConfigMatcher("mytest.config.js", "mytest.config.ts"),
// Match content patterns (optional)
matchers.NewContentMatcherFromStrings(
`\bmytest\s*\(`,
`\bmytest\.describe\s*\(`,
),
},
Priority: framework.PriorityGeneric,
}
// Register the framework
registry := framework.NewRegistry()
registry.Register(def)
// Find the framework by name
found := registry.Find("mytest")
fmt.Printf("Framework: %s\n", found.Name)
fmt.Printf("Priority: %d\n", found.Priority)
fmt.Printf("Languages: %v\n", found.Languages)
fmt.Printf("Matchers: %d\n", len(found.Matchers))
}
Output: Framework: mytest Priority: 100 Languages: [typescript javascript] Matchers: 3
Example (Matcher) ¶
ExampleMatcher demonstrates how to use matchers for framework detection.
package main
import (
"context"
"fmt"
"github.com/specvital/core/pkg/parser/framework"
"github.com/specvital/core/pkg/parser/framework/matchers"
)
func main() {
// Create matchers
importMatcher := matchers.NewImportMatcher("vitest", "vitest/")
configMatcher := matchers.NewConfigMatcher("vitest.config.ts")
ctx := context.Background()
// Test import signal
importSignal := framework.Signal{
Type: framework.SignalImport,
Value: "vitest/config",
}
result := importMatcher.Match(ctx, importSignal)
fmt.Printf("Import match confidence: %d\n", result.Confidence)
// Test config signal
configSignal := framework.Signal{
Type: framework.SignalConfigFile,
Value: "/project/vitest.config.ts",
}
result = configMatcher.Match(ctx, configSignal)
fmt.Printf("Config match confidence: %d\n", result.Confidence)
}
Output: Import match confidence: 100 Config match confidence: 100
Example (ProjectScope) ¶
ExampleProjectScope demonstrates project-wide configuration management.
package main
import (
"fmt"
"github.com/specvital/core/pkg/parser/framework"
)
func main() {
// Create a project scope
scope := framework.NewProjectScope()
// Add framework configurations
scope.AddConfig("jest.config.js", &framework.ConfigScope{
Framework: "jest",
ConfigPath: "jest.config.js",
GlobalsMode: true,
TestPatterns: []string{
"**/*.test.js",
"**/*.spec.js",
},
ExcludePatterns: []string{
"**/node_modules/**",
},
})
scope.AddConfig("apps/web/vitest.config.ts", &framework.ConfigScope{
Framework: "vitest",
ConfigPath: "apps/web/vitest.config.ts",
GlobalsMode: false,
TestPatterns: []string{
"**/*.test.ts",
},
})
// Find configuration
jestConfig := scope.FindConfig("jest.config.js")
fmt.Printf("Jest config globals mode: %t\n", jestConfig.GlobalsMode)
// Check if config exists
hasVitest := scope.HasConfigFile("apps/web/vitest.config.ts")
fmt.Printf("Has Vitest config: %t\n", hasVitest)
}
Output: Jest config globals mode: true Has Vitest config: true
Example (Registry) ¶
ExampleRegistry demonstrates registry operations.
package main
import (
"fmt"
"github.com/specvital/core/pkg/domain"
"github.com/specvital/core/pkg/parser/framework"
)
func main() {
// Create a new registry
reg := framework.NewRegistry()
// Register multiple frameworks
reg.Register(&framework.Definition{
Name: "jest",
Languages: []domain.Language{domain.LanguageTypeScript},
Priority: framework.PriorityGeneric,
})
reg.Register(&framework.Definition{
Name: "vitest",
Languages: []domain.Language{domain.LanguageTypeScript},
Priority: framework.PrioritySpecialized,
})
reg.Register(&framework.Definition{
Name: "playwright",
Languages: []domain.Language{domain.LanguageTypeScript},
Priority: framework.PriorityE2E,
})
// Get all frameworks (sorted by priority)
all := reg.All()
fmt.Println("Frameworks by priority:")
for _, def := range all {
fmt.Printf(" %s (priority: %d)\n", def.Name, def.Priority)
}
// Find by language
tsFrameworks := reg.FindByLanguage(domain.LanguageTypeScript)
fmt.Printf("\nTypeScript frameworks: %d\n", len(tsFrameworks))
// Find by name
jest := reg.Find("jest")
fmt.Printf("Found framework: %s\n", jest.Name)
}
Output: Frameworks by priority: vitest (priority: 200) playwright (priority: 150) jest (priority: 100) TypeScript frameworks: 3 Found framework: jest
Index ¶
Examples ¶
Constants ¶
const ( // PriorityGeneric is for common, general-purpose test frameworks. // These frameworks typically support a wide range of test files and patterns. // Examples: Jest, Go testing PriorityGeneric = 100 // PriorityE2E is for end-to-end testing frameworks. // These frameworks are more specialized and should be checked before generic frameworks. // Examples: Playwright, Cypress PriorityE2E = 150 // PrioritySpecialized is for highly specialized frameworks with unique characteristics. // These should be checked first to avoid false matches with generic frameworks. // Examples: Vitest with globals mode (requires explicit import detection) PrioritySpecialized = 200 )
Priority constants determine the order in which frameworks are checked during detection. Higher priority frameworks are evaluated first.
Use increments of 50 to allow for future insertions between priority levels.
const ( FrameworkCargoTest = "cargo-test" FrameworkCypress = "cypress" FrameworkGoTesting = "go-testing" FrameworkGTest = "gtest" FrameworkJest = "jest" FrameworkJUnit5 = "junit5" FrameworkKotest = "kotest" FrameworkMinitest = "minitest" FrameworkMocha = "mocha" FrameworkMSTest = "mstest" FrameworkNUnit = "nunit" FrameworkPHPUnit = "phpunit" FrameworkPlaywright = "playwright" FrameworkPytest = "pytest" FrameworkRSpec = "rspec" FrameworkTestNG = "testng" FrameworkUnittest = "unittest" FrameworkVitest = "vitest" FrameworkXCTest = "xctest" FrameworkXUnit = "xunit" )
Common framework names as constants to ensure consistency.
Variables ¶
This section is empty.
Functions ¶
func Register ¶
func Register(def *Definition)
Register adds a framework definition to the default registry. Typically called from framework package init() functions.
Types ¶
type AggregatedProjectScope ¶
type AggregatedProjectScope struct {
Configs map[string]*ConfigScope
ConfigFiles []string
}
AggregatedProjectScope aggregates multiple ConfigScope instances for hierarchical config resolution.
func NewProjectScope ¶
func NewProjectScope() *AggregatedProjectScope
func (*AggregatedProjectScope) AddConfig ¶
func (ps *AggregatedProjectScope) AddConfig(path string, scope *ConfigScope)
func (*AggregatedProjectScope) FindConfig ¶
func (ps *AggregatedProjectScope) FindConfig(path string) *ConfigScope
func (*AggregatedProjectScope) HasConfigFile ¶
func (ps *AggregatedProjectScope) HasConfigFile(filename string) bool
type ConfigParser ¶
type ConfigParser interface {
// Parse reads and interprets a framework configuration file.
// Returns ConfigScope containing parsed settings like test patterns, globals mode, etc.
// Returns error if the config file cannot be parsed.
Parse(ctx context.Context, configPath string, content []byte) (*ConfigScope, error)
}
ConfigParser extracts configuration information from framework config files.
type ConfigScope ¶
type ConfigScope struct {
ConfigPath string
BaseDir string
Include []string
Exclude []string
Settings map[string]interface{}
Projects []ProjectScope
Framework string
TestPatterns []string
ExcludePatterns []string
RootDir string
// GlobalsMode: when true, test files don't need explicit imports (e.g., Jest default).
GlobalsMode bool
}
ConfigScope defines the effective scope of a config file. Handles root resolution, include/exclude patterns, and workspace projects.
func NewConfigScope ¶
func NewConfigScope(configPath string, root string) *ConfigScope
NewConfigScope creates a ConfigScope with root resolved relative to config directory.
func (*ConfigScope) Contains ¶
func (s *ConfigScope) Contains(filePath string) bool
Contains checks if filePath is within this config's scope.
func (*ConfigScope) Depth ¶
func (s *ConfigScope) Depth() int
Depth returns the directory depth of BaseDir (used for selecting nearest config).
func (*ConfigScope) FindMatchingProject ¶
func (s *ConfigScope) FindMatchingProject(filePath string) *ProjectScope
FindMatchingProject finds the most specific project for a file.
type Definition ¶
type Definition struct {
// Name is the framework identifier (e.g., "jest", "vitest", "playwright").
Name string
// Languages specifies which programming languages this framework supports.
Languages []domain.Language
// Matchers are detection rules that determine if a test file uses this framework.
// Multiple matchers can be registered for different detection strategies
// (import statements, config files, file content patterns, etc.).
Matchers []Matcher
// ConfigParser extracts configuration information from framework config files.
// Used to determine settings like globals mode, test patterns, etc.
// May be nil if the framework doesn't have configuration files.
ConfigParser ConfigParser
// Parser extracts test definitions from source code files.
// Produces a domain.TestFile with all test suites and test cases.
Parser Parser
// Priority determines detection order when multiple frameworks could match.
// Higher priority frameworks are checked first.
// Use PriorityGeneric (100), PriorityE2E (150), or PrioritySpecialized (200).
Priority int
}
Definition represents a unified test framework specification that combines: - Framework identity (Name, Languages) - Detection components (Matchers) - Configuration parsing (ConfigParser) - Test file parsing (Parser) - Priority for detection ordering
Each framework (Jest, Vitest, Playwright, Go testing) provides a Definition that is registered with the global Registry.
func Find ¶
func Find(name string) *Definition
func FindByLanguage ¶
func FindByLanguage(lang domain.Language) []*Definition
FindByLanguage returns frameworks supporting the language (sorted by priority).
type MatchResult ¶
type MatchResult struct {
// Confidence is a score from 0-100 indicating how certain the match is.
// 0 = no match, 100 = definite match.
Confidence int
// Evidence is a list of specific indicators that support this match.
// For example: ["import '@jest/globals'", "jest.config.js found"].
Evidence []string
// Negative indicates this is a definite non-match.
// If true, this framework should be excluded from consideration.
// For example, Vitest with globals:false should not match files without imports.
Negative bool
}
MatchResult contains the outcome of a matcher evaluation.
func DefiniteMatch ¶
func DefiniteMatch(evidence ...string) MatchResult
DefiniteMatch returns a MatchResult indicating a definite match with evidence.
func NegativeMatch ¶
func NegativeMatch(evidence ...string) MatchResult
NegativeMatch returns a MatchResult indicating this framework definitely does not match.
func NoMatch ¶
func NoMatch() MatchResult
NoMatch returns a MatchResult indicating no match was found.
func PartialMatch ¶
func PartialMatch(confidence int, evidence ...string) MatchResult
PartialMatch returns a MatchResult with a specific confidence level and evidence.
type Matcher ¶
type Matcher interface {
// Match evaluates a signal and returns a confidence score.
// Returns MatchResult with confidence (0-100) and supporting evidence.
Match(ctx context.Context, signal Signal) MatchResult
}
Matcher defines the interface for framework detection rules. Matchers analyze different signals (imports, config files, content patterns) to determine if a test file belongs to a specific framework.
type Parser ¶
type Parser interface {
// Parse analyzes source code and extracts test suites and test cases.
// Returns a domain.TestFile containing all discovered tests.
// Returns error if the file cannot be parsed or doesn't contain valid tests.
Parse(ctx context.Context, source []byte, filename string) (*domain.TestFile, error)
}
Parser extracts test definitions from source code files.
type ProjectScope ¶
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry manages registered framework definitions (thread-safe).
func DefaultRegistry ¶
func DefaultRegistry() *Registry
func NewRegistry ¶
func NewRegistry() *Registry
func (*Registry) All ¶
func (r *Registry) All() []*Definition
func (*Registry) Find ¶
func (r *Registry) Find(name string) *Definition
func (*Registry) FindByLanguage ¶
func (r *Registry) FindByLanguage(lang domain.Language) []*Definition
func (*Registry) Register ¶
func (r *Registry) Register(def *Definition)
type Signal ¶
type Signal struct {
// Type indicates what kind of signal this is (import, config file, etc.).
Type SignalType
// Value contains the signal data (import path, file name, content, etc.).
Value string
// Context provides additional metadata specific to the signal type.
// For example, file content for content-based matching.
Context interface{}
}
Signal represents a detection signal that matchers can evaluate.
type SignalType ¶
type SignalType int
SignalType categorizes different kinds of detection signals.
const ( // SignalImport represents an import statement (e.g., "import { test } from 'vitest'"). SignalImport SignalType = iota // SignalConfigFile represents a config file name (e.g., "jest.config.js"). SignalConfigFile // SignalFileContent represents file content patterns (e.g., "test.describe("). SignalFileContent // SignalFileName represents a test file name pattern (e.g., "*.test.ts"). SignalFileName )