From abc76b865f30ee342f054d620cc7cc933bf0741f Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Wed, 13 May 2026 10:30:38 +0200 Subject: [PATCH 1/2] add native Effect diagnostics API --- .changeset/effect-diagnostics-api.md | 7 + _patches/001-cmd-tsgo-main.patch | 3 +- _patches/028-api-effect-diagnostics.patch | 120 +++++++++++ _tools/gen_shims/main.go | 1 + etsapihooks/doc.go | 5 + etsapihooks/init.go | 25 +++ etscheckerhooks/init.go | 246 +-------------------- go.mod | 2 + internal/rulerunner/diagnostics.go | 249 ++++++++++++++++++++++ shim/api/go.mod | 5 + shim/api/shim.go | 218 +++++++++++++++++++ 11 files changed, 638 insertions(+), 243 deletions(-) create mode 100644 .changeset/effect-diagnostics-api.md create mode 100644 _patches/028-api-effect-diagnostics.patch create mode 100644 etsapihooks/doc.go create mode 100644 etsapihooks/init.go create mode 100644 internal/rulerunner/diagnostics.go create mode 100644 shim/api/go.mod create mode 100644 shim/api/shim.go diff --git a/.changeset/effect-diagnostics-api.md b/.changeset/effect-diagnostics-api.md new file mode 100644 index 00000000..a92240a2 --- /dev/null +++ b/.changeset/effect-diagnostics-api.md @@ -0,0 +1,7 @@ +--- +"@effect/tsgo": minor +--- + +Add a native `getEffectDiagnostics` API entrypoint that runs Effect diagnostics for a specific source file with explicit rule selection and Effect options. + +This also exposes a shared internal rule runner so the checker hook and native API use the same directive, severity, and override behavior. diff --git a/_patches/001-cmd-tsgo-main.patch b/_patches/001-cmd-tsgo-main.patch index f1016965..1e10478d 100644 --- a/_patches/001-cmd-tsgo-main.patch +++ b/_patches/001-cmd-tsgo-main.patch @@ -2,11 +2,12 @@ diff --git a/cmd/tsgo/main.go b/cmd/tsgo/main.go index a2fe02e8c..10044042c 100644 --- a/cmd/tsgo/main.go +++ b/cmd/tsgo/main.go -@@ -3,7 +3,12 @@ package main +@@ -3,6 +3,12 @@ package main import ( "os" + // Import Effect hooks to register via init() ++ _ "github.com/effect-ts/tsgo/etsapihooks" // native API hooks + _ "github.com/effect-ts/tsgo/etsexecutehooks" // exit-code filtering hooks + _ "github.com/effect-ts/tsgo/etscheckerhooks" // checker diagnostics hooks + _ "github.com/effect-ts/tsgo/etslshooks" // LS codefix hooks diff --git a/_patches/028-api-effect-diagnostics.patch b/_patches/028-api-effect-diagnostics.patch new file mode 100644 index 00000000..4976cce0 --- /dev/null +++ b/_patches/028-api-effect-diagnostics.patch @@ -0,0 +1,120 @@ +diff --git a/internal/api/proto.go b/internal/api/proto.go +--- a/internal/api/proto.go ++++ b/internal/api/proto.go +@@ -6,6 +6,7 @@ import ( + "strconv" + "strings" + ++ "github.com/effect-ts/tsgo/etscore" + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/core" +@@ -156,6 +157,7 @@ const ( + // Diagnostic methods + MethodGetSyntacticDiagnostics Method = "getSyntacticDiagnostics" + MethodGetSemanticDiagnostics Method = "getSemanticDiagnostics" ++ MethodGetEffectDiagnostics Method = "getEffectDiagnostics" + MethodGetSuggestionDiagnostics Method = "getSuggestionDiagnostics" + MethodGetDeclarationDiagnostics Method = "getDeclarationDiagnostics" + MethodGetConfigFileParsingDiagnostics Method = "getConfigFileParsingDiagnostics" +@@ -389,6 +391,7 @@ var unmarshalers = map[Method]func(json.Value) (any, error){ + MethodGetESSymbolType: unmarshallerFor[GetIntrinsicTypeParams], + MethodGetSyntacticDiagnostics: unmarshallerFor[GetDiagnosticsParams], + MethodGetSemanticDiagnostics: unmarshallerFor[GetDiagnosticsParams], ++ MethodGetEffectDiagnostics: unmarshallerFor[GetEffectDiagnosticsParams], + MethodGetSuggestionDiagnostics: unmarshallerFor[GetDiagnosticsParams], + MethodGetDeclarationDiagnostics: unmarshallerFor[GetDiagnosticsParams], + MethodGetConfigFileParsingDiagnostics: unmarshallerFor[GetProjectDiagnosticsParams], +@@ -863,6 +866,15 @@ type GetDiagnosticsParams struct { + File *DocumentIdentifier `json:"file,omitempty"` + } + ++// GetEffectDiagnosticsParams are parameters for Effect diagnostic rules. ++type GetEffectDiagnosticsParams struct { ++ Snapshot Handle[project.Snapshot] `json:"snapshot"` ++ Project Handle[project.Project] `json:"project"` ++ File DocumentIdentifier `json:"file"` ++ Options *etscore.EffectPluginOptions `json:"options,omitempty"` ++ Rules []string `json:"rules,omitempty"` ++} ++ + // GetProjectDiagnosticsParams are parameters for project-wide diagnostic methods. + type GetProjectDiagnosticsParams struct { + Snapshot Handle[project.Snapshot] `json:"snapshot"` +diff --git a/internal/api/session.go b/internal/api/session.go +--- a/internal/api/session.go ++++ b/internal/api/session.go +@@ -7,6 +7,7 @@ import ( + "sync" + "sync/atomic" + ++ "github.com/effect-ts/tsgo/etscore" + "github.com/microsoft/typescript-go/internal/api/encoder" + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" +@@ -21,6 +22,15 @@ import ( + "github.com/microsoft/typescript-go/internal/tspath" + ) + ++// GetEffectDiagnosticsCallback is invoked by the API getEffectDiagnostics method. ++// It is registered by external packages to keep Effect rule evaluation outside TypeScript-Go. ++var GetEffectDiagnosticsCallback func(ctx context.Context, program *compiler.Program, c *checker.Checker, sourceFile *ast.SourceFile, options *etscore.EffectPluginOptions, rules []string) ([]*ast.Diagnostic, error) ++ ++// RegisterGetEffectDiagnosticsCallback registers the getEffectDiagnostics API callback. ++func RegisterGetEffectDiagnosticsCallback(cb func(ctx context.Context, program *compiler.Program, c *checker.Checker, sourceFile *ast.SourceFile, options *etscore.EffectPluginOptions, rules []string) ([]*ast.Diagnostic, error)) { ++ GetEffectDiagnosticsCallback = cb ++} ++ + var sessionIDCounter atomic.Uint64 + + // snapshotData holds the per-snapshot state including the snapshot itself +@@ -459,6 +469,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. + return s.handleGetSyntacticDiagnostics(ctx, parsed.(*GetDiagnosticsParams)) + case string(MethodGetSemanticDiagnostics): + return s.handleGetSemanticDiagnostics(ctx, parsed.(*GetDiagnosticsParams)) ++ case string(MethodGetEffectDiagnostics): ++ return s.handleGetEffectDiagnostics(ctx, parsed.(*GetEffectDiagnosticsParams)) + case string(MethodGetSuggestionDiagnostics): + return s.handleGetSuggestionDiagnostics(ctx, parsed.(*GetDiagnosticsParams)) + case string(MethodGetDeclarationDiagnostics): +@@ -2003,6 +2015,40 @@ func (s *Session) handleGetSemanticDiagnostics(ctx context.Context, params *GetD + return NewDiagnosticResponses(diags), nil + } + ++// handleGetEffectDiagnostics returns Effect diagnostics for a file using explicit API options. ++func (s *Session) handleGetEffectDiagnostics(ctx context.Context, params *GetEffectDiagnosticsParams) ([]*DiagnosticResponse, error) { ++ if GetEffectDiagnosticsCallback == nil { ++ return nil, fmt.Errorf("getEffectDiagnostics callback is not registered") ++ } ++ ++ sd, err := s.getSnapshotData(params.Snapshot) ++ if err != nil { ++ return nil, err ++ } ++ ++ program, err := sd.getProgram(params.Project) ++ if err != nil { ++ return nil, err ++ } ++ ++ sourceFile := program.GetSourceFile(params.File.ToFileName()) ++ if sourceFile == nil { ++ return nil, fmt.Errorf("source file not found: %s", params.File.ToFileName()) ++ } ++ ++ c, done := program.GetTypeCheckerForFileExclusive(ctx, sourceFile) ++ defer done() ++ ++ // Ensure the source file is checked before running rules that rely on type information. ++ c.GetDiagnostics(ctx, sourceFile) ++ ++ diags, err := GetEffectDiagnosticsCallback(ctx, program, c, sourceFile, params.Options, params.Rules) ++ if err != nil { ++ return nil, err ++ } ++ return NewDiagnosticResponses(diags), nil ++} ++ + // handleGetSuggestionDiagnostics returns suggestion diagnostics for a file or all files. + func (s *Session) handleGetSuggestionDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) diff --git a/_tools/gen_shims/main.go b/_tools/gen_shims/main.go index 02e06d5e..9720874d 100644 --- a/_tools/gen_shims/main.go +++ b/_tools/gen_shims/main.go @@ -48,6 +48,7 @@ type ExtraShim struct { func main() { packagesToShim := []string{ + "api", "ast", "astnav", "bundled", diff --git a/etsapihooks/doc.go b/etsapihooks/doc.go new file mode 100644 index 00000000..82645155 --- /dev/null +++ b/etsapihooks/doc.go @@ -0,0 +1,5 @@ +// Package etsapihooks registers Effect API hooks for TypeScript-Go. +// +// This package should be imported with a blank identifier by the main entry point +// to register API-only Effect diagnostics. +package etsapihooks diff --git a/etsapihooks/init.go b/etsapihooks/init.go new file mode 100644 index 00000000..1ddfaa31 --- /dev/null +++ b/etsapihooks/init.go @@ -0,0 +1,25 @@ +package etsapihooks + +import ( + "context" + + "github.com/effect-ts/tsgo/etscore" + "github.com/effect-ts/tsgo/internal/effectconfigraw" + "github.com/effect-ts/tsgo/internal/rulerunner" + "github.com/microsoft/typescript-go/shim/api" + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/checker" + "github.com/microsoft/typescript-go/shim/compiler" +) + +func init() { + effectconfigraw.Register() + api.RegisterGetEffectDiagnosticsCallback(getEffectDiagnostics) +} + +func getEffectDiagnostics(ctx context.Context, program *compiler.Program, c *checker.Checker, sf *ast.SourceFile, options *etscore.EffectPluginOptions, ruleNames []string) ([]*ast.Diagnostic, error) { + if options == nil { + options = program.Options().Effect + } + return rulerunner.Run(ctx, program, c, sf, options, ruleNames) +} diff --git a/etscheckerhooks/init.go b/etscheckerhooks/init.go index 63acc149..255b70e9 100644 --- a/etscheckerhooks/init.go +++ b/etscheckerhooks/init.go @@ -7,17 +7,11 @@ import ( "context" "github.com/effect-ts/tsgo/etscore" - "github.com/effect-ts/tsgo/internal/directives" "github.com/effect-ts/tsgo/internal/effectconfigraw" - "github.com/effect-ts/tsgo/internal/pluginoptions" - "github.com/effect-ts/tsgo/internal/rule" - "github.com/effect-ts/tsgo/internal/rules" - "github.com/effect-ts/tsgo/internal/typeparser" + "github.com/effect-ts/tsgo/internal/rulerunner" "github.com/microsoft/typescript-go/shim/ast" "github.com/microsoft/typescript-go/shim/checker" "github.com/microsoft/typescript-go/shim/core" - tsdiag "github.com/microsoft/typescript-go/shim/diagnostics" - "github.com/microsoft/typescript-go/shim/scanner" ) // init registers the Effect diagnostics callbacks with TypeScript-Go. @@ -35,246 +29,14 @@ func getEffectConfig(p checker.Program) *etscore.EffectPluginOptions { return p.Options().Effect } -// RuleDiagnostic pairs a diagnostic with its rule for directive processing. -type RuleDiagnostic struct { - RuleName string - Rule *rule.Rule - Diagnostic *ast.Diagnostic -} - // afterCheckSourceFile is called after type checking each source file. // It runs Effect diagnostics if the plugin is enabled. func afterCheckSourceFile(ctx context.Context, program checker.Program, c *checker.Checker, sf *ast.SourceFile) { - if sf.IsDeclarationFile || program.IsSourceFileFromExternalLibrary(sf) { - return - } - - // Get Effect config from program options (parsed during config loading) - effectConfig := getEffectConfig(program) - if effectConfig == nil { - return - } - effectiveConfig := pluginoptions.ResolveEffectPluginOptionsForSourceFile( - effectConfig, - sf.FileName(), - program.Options().ConfigFilePath, - program.UseCaseSensitiveFileNames(), - ) - - resolvedSeverity := pluginoptions.ResolveDiagnosticSeverityForFile( - effectConfig, - sf.FileName(), - program.Options().ConfigFilePath, - program.UseCaseSensitiveFileNames(), - ) - - // Check if diagnostics are enabled (nil DiagnosticSeverity map means explicitly disabled) - if !etscore.DiagnosticsEnabled(effectConfig) || resolvedSeverity == nil { - return - } - - tp := typeparser.NewTypeParser(program, c) - - // Collect directives from source file for suppression support - sourceText := sf.Text() - effectDirectives := directives.CollectEffectDirectives(sourceText) - directiveSet := directives.BuildDirectiveSet(effectDirectives) - - // Check for file-level skip-file directive for all rules - if directiveSet.IsSkipFile("*") { + diagnostics, err := rulerunner.Run(ctx, program, c, sf, getEffectConfig(program), nil) + if err != nil { return } - - // Collect all diagnostics from enabled rules - allDiagnostics := collectDiagnostics(ctx, program, c, tp, sf, effectConfig, effectiveConfig, resolvedSeverity, directiveSet) - - // Transform and filter diagnostics based on directives - finalDiagnostics := transformDiagnostics(allDiagnostics, sf, directiveSet, effectConfig, resolvedSeverity) - - // Emit final diagnostics - for _, diag := range finalDiagnostics { + for _, diag := range diagnostics { c.AddDiagnostic(diag) } - - // Report unused next-line directives - reportUnusedDirectives(c, sf, effectDirectives, directiveSet, resolvedSeverity) -} - -// collectDiagnostics runs all enabled rules and collects their diagnostics. -func collectDiagnostics( - ctx context.Context, - program checker.Program, - c *checker.Checker, - tp *typeparser.TypeParser, - sf *ast.SourceFile, - globalConfig *etscore.EffectPluginOptions, - options *etscore.ResolvedEffectPluginOptions, - resolvedSeverity map[string]etscore.Severity, - directiveSet *directives.DirectiveSet, -) []*RuleDiagnostic { - var results []*RuleDiagnostic - - for i := range rules.All { - r := &rules.All[i] - // Determine effective severity: use explicit config if set, otherwise rule's default - configSeverity, configuredExplicitly := severityFromMap(resolvedSeverity, r.Name) - if !configuredExplicitly { - configSeverity = r.DefaultSeverity - } - // Skip rules that are off, unless a directive in the source file enables them - // or skipDisabledOptimization is set (which bypasses this optimization entirely) - if !globalConfig.SkipDisabledOptimization && configSeverity.IsOff() && !directiveSet.HasEnablingDirective(r.Name) { - continue - } - - // Skip rules with file-level skip-file directive - if directiveSet.IsSkipFile(r.Name) { - continue - } - - // Run the rule - ruleCtx := rule.NewContext(ctx, program, c, tp, sf, options, r.DefaultSeverity) - diags := r.Run(ruleCtx) - - // Tag each diagnostic with its rule for directive lookup - for _, diag := range diags { - results = append(results, &RuleDiagnostic{ - RuleName: r.Name, - Rule: r, - Diagnostic: diag, - }) - } - } - - return results -} - -// transformDiagnostics applies directive transformations to diagnostics. -// Returns a new slice of diagnostics with potentially modified severities. -// Diagnostics may be filtered out if their effective severity is "off". -func transformDiagnostics( - diags []*RuleDiagnostic, - sf *ast.SourceFile, - directiveSet *directives.DirectiveSet, - globalConfig *etscore.EffectPluginOptions, - resolvedSeverity map[string]etscore.Severity, -) []*ast.Diagnostic { - var results []*ast.Diagnostic - lineMap := sf.ECMALineMap() - - for _, rd := range diags { - // Get diagnostic line number - line := scanner.ComputeLineOfPosition(lineMap, rd.Diagnostic.Pos()) - - // Get default severity: use explicit config if set, otherwise rule's default - defaultSeverity, configuredExplicitly := severityFromMap(resolvedSeverity, rd.RuleName) - if !configuredExplicitly { - defaultSeverity = rd.Rule.DefaultSeverity - } - - // Get effective severity considering directives - effectiveSeverity := directiveSet.GetEffectiveSeverityAndMarkUsed( - rd.RuleName, - line, - defaultSeverity, - ) - - // Skip if severity is off - if effectiveSeverity.IsOff() { - continue - } - - // Transform diagnostic if severity changed - originalCategory := rd.Diagnostic.Category() - newCategory := directives.ToCategory(effectiveSeverity) - - // In CLI mode, filter or convert suggestion/message diagnostics - if etscore.IsCommandLineMode() { - if !globalConfig.GetIncludeSuggestionsInTsc() { - // Drop suggestion and message diagnostics entirely - if newCategory == tsdiag.CategorySuggestion || newCategory == tsdiag.CategoryMessage { - continue - } - } - } - - if originalCategory != newCategory { - transformed := createTransformedDiagnostic(rd.Diagnostic, newCategory) - results = append(results, transformed) - } else { - // No change needed - results = append(results, rd.Diagnostic) - } - } - - return results -} - -// createTransformedDiagnostic creates a new diagnostic with a different category. -// This is the immutable approach - we don't mutate the original. -func createTransformedDiagnostic( - original *ast.Diagnostic, - newCategory tsdiag.Category, -) *ast.Diagnostic { - return ast.NewDiagnosticFromSerialized( - original.File(), - core.NewTextRange(original.Pos(), original.End()), - original.Code(), - newCategory, - original.MessageKey(), - original.MessageArgs(), - original.MessageChain(), - original.RelatedInformation(), - original.ReportsUnnecessary(), - original.ReportsDeprecated(), - original.SkippedOnNoEmit(), - ) -} - -// reportUnusedDirectives emits warnings for next-line directives that didn't suppress any diagnostic. -func reportUnusedDirectives( - c *checker.Checker, - sf *ast.SourceFile, - allDirectives []directives.Directive, - directiveSet *directives.DirectiveSet, - resolvedSeverity map[string]etscore.Severity, -) { - // Get configured severity, defaulting to warning (per spec) - severity, ok := severityFromMap(resolvedSeverity, "unusedDirective") - if !ok { - severity = etscore.SeverityWarning // default for unusedDirective - } - if severity.IsOff() { - return - } - - unused := directiveSet.GetUnusedNextLineDirectives(allDirectives) - if len(unused) == 0 { - return - } - - for _, d := range unused { - diag := ast.NewDiagnosticFromSerialized( - sf, - core.NewTextRange(d.Pos, d.End), - tsdiag.X_effect_diagnostics_directive_has_no_effect.Code(), - directives.ToCategory(severity), - tsdiag.X_effect_diagnostics_directive_has_no_effect.Key(), - nil, // messageArgs - nil, // messageChain - nil, // relatedInformation - false, // reportsUnnecessary - false, // reportsDeprecated - false, // skippedOnNoEmit - ) - c.AddDiagnostic(diag) - } -} - -func severityFromMap(resolved map[string]etscore.Severity, ruleName string) (etscore.Severity, bool) { - if resolved == nil { - return etscore.SeverityError, false - } - severity, ok := resolved[ruleName] - return severity, ok } diff --git a/go.mod b/go.mod index 78d156f0..cbbbd4ea 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.26 replace ( github.com/microsoft/typescript-go => ./typescript-go + github.com/microsoft/typescript-go/shim/api => ./shim/api github.com/microsoft/typescript-go/shim/ast => ./shim/ast github.com/microsoft/typescript-go/shim/astnav => ./shim/astnav github.com/microsoft/typescript-go/shim/bundled => ./shim/bundled @@ -45,6 +46,7 @@ replace ( require ( github.com/effect-ts/tsgo/etscore v0.0.0 + github.com/microsoft/typescript-go/shim/api v0.0.0 github.com/microsoft/typescript-go/shim/ast v0.0.0 github.com/microsoft/typescript-go/shim/astnav v0.0.0 github.com/microsoft/typescript-go/shim/bundled v0.0.0-00010101000000-000000000000 diff --git a/internal/rulerunner/diagnostics.go b/internal/rulerunner/diagnostics.go new file mode 100644 index 00000000..6e8950cb --- /dev/null +++ b/internal/rulerunner/diagnostics.go @@ -0,0 +1,249 @@ +package rulerunner + +import ( + "context" + "fmt" + "slices" + + "github.com/effect-ts/tsgo/etscore" + "github.com/effect-ts/tsgo/internal/directives" + "github.com/effect-ts/tsgo/internal/pluginoptions" + "github.com/effect-ts/tsgo/internal/rule" + "github.com/effect-ts/tsgo/internal/rules" + "github.com/effect-ts/tsgo/internal/typeparser" + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/checker" + "github.com/microsoft/typescript-go/shim/core" + tsdiag "github.com/microsoft/typescript-go/shim/diagnostics" + "github.com/microsoft/typescript-go/shim/scanner" +) + +// RuleDiagnostic pairs a diagnostic with its rule for directive processing. +type RuleDiagnostic struct { + RuleName string + Rule *rule.Rule + Diagnostic *ast.Diagnostic +} + +// Run executes Effect diagnostics for a source file and returns diagnostics without emitting them. +func Run(ctx context.Context, program checker.Program, c *checker.Checker, sf *ast.SourceFile, effectConfig *etscore.EffectPluginOptions, ruleNames []string) ([]*ast.Diagnostic, error) { + if sf.IsDeclarationFile || program.IsSourceFileFromExternalLibrary(sf) { + return nil, nil + } + + if effectConfig == nil { + return nil, nil + } + + effectiveConfig := pluginoptions.ResolveEffectPluginOptionsForSourceFile( + effectConfig, + sf.FileName(), + program.Options().ConfigFilePath, + program.UseCaseSensitiveFileNames(), + ) + + resolvedSeverity := pluginoptions.ResolveDiagnosticSeverityForFile( + effectConfig, + sf.FileName(), + program.Options().ConfigFilePath, + program.UseCaseSensitiveFileNames(), + ) + + if !etscore.DiagnosticsEnabled(effectConfig) || resolvedSeverity == nil { + return nil, nil + } + + selectedRules, err := selectRules(ruleNames) + if err != nil { + return nil, err + } + if len(selectedRules) == 0 { + return nil, nil + } + + tp := typeparser.NewTypeParser(program, c) + sourceText := sf.Text() + effectDirectives := directives.CollectEffectDirectives(sourceText) + directiveSet := directives.BuildDirectiveSet(effectDirectives) + + if directiveSet.IsSkipFile("*") { + return nil, nil + } + + allDiagnostics := collectDiagnostics(ctx, program, c, tp, sf, effectConfig, effectiveConfig, resolvedSeverity, directiveSet, selectedRules) + finalDiagnostics := transformDiagnostics(allDiagnostics, sf, directiveSet, effectConfig, resolvedSeverity) + finalDiagnostics = append(finalDiagnostics, unusedDirectiveDiagnostics(sf, effectDirectives, directiveSet, resolvedSeverity)...) + + return finalDiagnostics, nil +} + +func selectRules(ruleNames []string) ([]*rule.Rule, error) { + if ruleNames != nil && len(ruleNames) == 0 { + return nil, nil + } + + if ruleNames == nil { + selected := make([]*rule.Rule, 0, len(rules.All)) + for i := range rules.All { + selected = append(selected, &rules.All[i]) + } + return selected, nil + } + + selected := make([]*rule.Rule, 0, len(ruleNames)) + for _, name := range ruleNames { + idx := slices.IndexFunc(rules.All, func(r rule.Rule) bool { return r.Name == name }) + if idx == -1 { + return nil, fmt.Errorf("unknown Effect diagnostic rule %q", name) + } + selected = append(selected, &rules.All[idx]) + } + return selected, nil +} + +// collectDiagnostics runs all enabled rules and collects their diagnostics. +func collectDiagnostics( + ctx context.Context, + program checker.Program, + c *checker.Checker, + tp *typeparser.TypeParser, + sf *ast.SourceFile, + globalConfig *etscore.EffectPluginOptions, + options *etscore.ResolvedEffectPluginOptions, + resolvedSeverity map[string]etscore.Severity, + directiveSet *directives.DirectiveSet, + selectedRules []*rule.Rule, +) []*RuleDiagnostic { + var results []*RuleDiagnostic + + for _, r := range selectedRules { + configSeverity, configuredExplicitly := severityFromMap(resolvedSeverity, r.Name) + if !configuredExplicitly { + configSeverity = r.DefaultSeverity + } + if !globalConfig.SkipDisabledOptimization && configSeverity.IsOff() && !directiveSet.HasEnablingDirective(r.Name) { + continue + } + + if directiveSet.IsSkipFile(r.Name) { + continue + } + + ruleCtx := rule.NewContext(ctx, program, c, tp, sf, options, r.DefaultSeverity) + diags := r.Run(ruleCtx) + + for _, diag := range diags { + results = append(results, &RuleDiagnostic{ + RuleName: r.Name, + Rule: r, + Diagnostic: diag, + }) + } + } + + return results +} + +func transformDiagnostics( + diags []*RuleDiagnostic, + sf *ast.SourceFile, + directiveSet *directives.DirectiveSet, + globalConfig *etscore.EffectPluginOptions, + resolvedSeverity map[string]etscore.Severity, +) []*ast.Diagnostic { + var results []*ast.Diagnostic + lineMap := sf.ECMALineMap() + + for _, rd := range diags { + line := scanner.ComputeLineOfPosition(lineMap, rd.Diagnostic.Pos()) + + defaultSeverity, configuredExplicitly := severityFromMap(resolvedSeverity, rd.RuleName) + if !configuredExplicitly { + defaultSeverity = rd.Rule.DefaultSeverity + } + + effectiveSeverity := directiveSet.GetEffectiveSeverityAndMarkUsed( + rd.RuleName, + line, + defaultSeverity, + ) + + if effectiveSeverity.IsOff() { + continue + } + + originalCategory := rd.Diagnostic.Category() + newCategory := directives.ToCategory(effectiveSeverity) + + if etscore.IsCommandLineMode() && !globalConfig.GetIncludeSuggestionsInTsc() { + if newCategory == tsdiag.CategorySuggestion || newCategory == tsdiag.CategoryMessage { + continue + } + } + + if originalCategory != newCategory { + results = append(results, createTransformedDiagnostic(rd.Diagnostic, newCategory)) + } else { + results = append(results, rd.Diagnostic) + } + } + + return results +} + +func createTransformedDiagnostic(original *ast.Diagnostic, newCategory tsdiag.Category) *ast.Diagnostic { + return ast.NewDiagnosticFromSerialized( + original.File(), + core.NewTextRange(original.Pos(), original.End()), + original.Code(), + newCategory, + original.MessageKey(), + original.MessageArgs(), + original.MessageChain(), + original.RelatedInformation(), + original.ReportsUnnecessary(), + original.ReportsDeprecated(), + original.SkippedOnNoEmit(), + ) +} + +func unusedDirectiveDiagnostics(sf *ast.SourceFile, allDirectives []directives.Directive, directiveSet *directives.DirectiveSet, resolvedSeverity map[string]etscore.Severity) []*ast.Diagnostic { + severity, ok := severityFromMap(resolvedSeverity, "unusedDirective") + if !ok { + severity = etscore.SeverityWarning + } + if severity.IsOff() { + return nil + } + + unused := directiveSet.GetUnusedNextLineDirectives(allDirectives) + if len(unused) == 0 { + return nil + } + + diags := make([]*ast.Diagnostic, 0, len(unused)) + for _, d := range unused { + diags = append(diags, ast.NewDiagnosticFromSerialized( + sf, + core.NewTextRange(d.Pos, d.End), + tsdiag.X_effect_diagnostics_directive_has_no_effect.Code(), + directives.ToCategory(severity), + tsdiag.X_effect_diagnostics_directive_has_no_effect.Key(), + nil, + nil, + nil, + false, + false, + false, + )) + } + return diags +} + +func severityFromMap(resolved map[string]etscore.Severity, ruleName string) (etscore.Severity, bool) { + if resolved == nil { + return etscore.SeverityError, false + } + severity, ok := resolved[ruleName] + return severity, ok +} diff --git a/shim/api/go.mod b/shim/api/go.mod new file mode 100644 index 00000000..0ecb9bef --- /dev/null +++ b/shim/api/go.mod @@ -0,0 +1,5 @@ +module github.com/microsoft/typescript-go/shim/api + +go 1.26 + +require github.com/microsoft/typescript-go v0.0.0 diff --git a/shim/api/shim.go b/shim/api/shim.go new file mode 100644 index 00000000..93ef0ed8 --- /dev/null +++ b/shim/api/shim.go @@ -0,0 +1,218 @@ + +// Code generated by _tools/gen_shims. DO NOT EDIT. + +package api + +import "context" +import "github.com/effect-ts/tsgo/etscore" +import "github.com/microsoft/typescript-go/internal/api" +import "github.com/microsoft/typescript-go/internal/ast" +import "github.com/microsoft/typescript-go/internal/checker" +import "github.com/microsoft/typescript-go/internal/compiler" +import "github.com/microsoft/typescript-go/internal/project" +import "io" +import _ "unsafe" + +type APIFileChangeSummary = api.APIFileChangeSummary +type APIFileChanges = api.APIFileChanges +type AsyncConn = api.AsyncConn +type CheckerSignatureParams = api.CheckerSignatureParams +type CheckerTypeParams = api.CheckerTypeParams +type ConfigFileResponse = api.ConfigFileResponse +type Conn = api.Conn +type DiagnosticResponse = api.DiagnosticResponse +type DocumentIdentifier = api.DocumentIdentifier +var ErrClientError = api.ErrClientError +var ErrConnClosed = api.ErrConnClosed +var ErrInvalidRequest = api.ErrInvalidRequest +var ErrRequestTimeout = api.ErrRequestTimeout +//go:linkname GeneratePipePath github.com/microsoft/typescript-go/internal/api.GeneratePipePath +func GeneratePipePath(name string) string +type GetBaseTypeOfLiteralTypeParams = api.GetBaseTypeOfLiteralTypeParams +type GetContextualTypeParams = api.GetContextualTypeParams +type GetDefaultProjectForFileParams = api.GetDefaultProjectForFileParams +type GetDiagnosticsParams = api.GetDiagnosticsParams +var GetEffectDiagnosticsCallback = api.GetEffectDiagnosticsCallback +type GetEffectDiagnosticsParams = api.GetEffectDiagnosticsParams +type GetExportSymbolOfSymbolParams = api.GetExportSymbolOfSymbolParams +type GetExportsOfSymbolParams = api.GetExportsOfSymbolParams +type GetIntrinsicTypeParams = api.GetIntrinsicTypeParams +type GetMembersOfSymbolParams = api.GetMembersOfSymbolParams +type GetNonNullableTypeParams = api.GetNonNullableTypeParams +type GetParameterTypeParams = api.GetParameterTypeParams +type GetParentOfSymbolParams = api.GetParentOfSymbolParams +type GetProjectDiagnosticsParams = api.GetProjectDiagnosticsParams +type GetResolvedSignatureParams = api.GetResolvedSignatureParams +type GetSignaturesOfTypeParams = api.GetSignaturesOfTypeParams +type GetSourceFileParams = api.GetSourceFileParams +type GetSymbolAtLocationParams = api.GetSymbolAtLocationParams +type GetSymbolAtPositionParams = api.GetSymbolAtPositionParams +type GetSymbolOfTypeParams = api.GetSymbolOfTypeParams +type GetSymbolsAtLocationsParams = api.GetSymbolsAtLocationsParams +type GetSymbolsAtPositionsParams = api.GetSymbolsAtPositionsParams +type GetTypeAtLocationParams = api.GetTypeAtLocationParams +type GetTypeAtLocationsParams = api.GetTypeAtLocationsParams +type GetTypeAtPositionParams = api.GetTypeAtPositionParams +type GetTypeFromTypeNodeParams = api.GetTypeFromTypeNodeParams +type GetTypeOfSymbolAtLocationParams = api.GetTypeOfSymbolAtLocationParams +type GetTypeOfSymbolParams = api.GetTypeOfSymbolParams +type GetTypePropertyParams = api.GetTypePropertyParams +type GetTypesAtPositionsParams = api.GetTypesAtPositionsParams +type GetTypesOfSymbolsParams = api.GetTypesOfSymbolsParams +type GetWidenedTypeParams = api.GetWidenedTypeParams +type Handle[T any] = api.Handle[T] +type Handler = api.Handler +type IndexInfoResponse = api.IndexInfoResponse +type InitializeResponse = api.InitializeResponse +type IsArrayLikeTypeParams = api.IsArrayLikeTypeParams +type JSONRPCProtocol = api.JSONRPCProtocol +type Message = api.Message +type MessagePackProtocol = api.MessagePackProtocol +type MessageType = api.MessageType +const MessageTypeCall = api.MessageTypeCall +const MessageTypeCallError = api.MessageTypeCallError +const MessageTypeCallResponse = api.MessageTypeCallResponse +const MessageTypeError = api.MessageTypeError +const MessageTypeRequest = api.MessageTypeRequest +const MessageTypeResponse = api.MessageTypeResponse +const MessageTypeUnknown = api.MessageTypeUnknown +type Method = api.Method +const MethodGetAnyType = api.MethodGetAnyType +const MethodGetBaseTypeOfLiteralType = api.MethodGetBaseTypeOfLiteralType +const MethodGetBaseTypeOfType = api.MethodGetBaseTypeOfType +const MethodGetBaseTypes = api.MethodGetBaseTypes +const MethodGetBigIntType = api.MethodGetBigIntType +const MethodGetBooleanType = api.MethodGetBooleanType +const MethodGetCheckTypeOfType = api.MethodGetCheckTypeOfType +const MethodGetConfigFileParsingDiagnostics = api.MethodGetConfigFileParsingDiagnostics +const MethodGetConstraintOfType = api.MethodGetConstraintOfType +const MethodGetConstraintOfTypeParameter = api.MethodGetConstraintOfTypeParameter +const MethodGetContextualType = api.MethodGetContextualType +const MethodGetDeclarationDiagnostics = api.MethodGetDeclarationDiagnostics +const MethodGetDeclaredTypeOfSymbol = api.MethodGetDeclaredTypeOfSymbol +const MethodGetDefaultProjectForFile = api.MethodGetDefaultProjectForFile +const MethodGetESSymbolType = api.MethodGetESSymbolType +const MethodGetEffectDiagnostics = api.MethodGetEffectDiagnostics +const MethodGetExportSymbolOfSymbol = api.MethodGetExportSymbolOfSymbol +const MethodGetExportsOfSymbol = api.MethodGetExportsOfSymbol +const MethodGetExtendsTypeOfType = api.MethodGetExtendsTypeOfType +const MethodGetIndexInfosOfType = api.MethodGetIndexInfosOfType +const MethodGetIndexTypeOfType = api.MethodGetIndexTypeOfType +const MethodGetLocalTypeParametersOfType = api.MethodGetLocalTypeParametersOfType +const MethodGetMembersOfSymbol = api.MethodGetMembersOfSymbol +const MethodGetNeverType = api.MethodGetNeverType +const MethodGetNonNullableType = api.MethodGetNonNullableType +const MethodGetNullType = api.MethodGetNullType +const MethodGetNumberType = api.MethodGetNumberType +const MethodGetObjectTypeOfType = api.MethodGetObjectTypeOfType +const MethodGetOuterTypeParametersOfType = api.MethodGetOuterTypeParametersOfType +const MethodGetParameterType = api.MethodGetParameterType +const MethodGetParentOfSymbol = api.MethodGetParentOfSymbol +const MethodGetPropertiesOfType = api.MethodGetPropertiesOfType +const MethodGetResolvedSignature = api.MethodGetResolvedSignature +const MethodGetRestTypeOfSignature = api.MethodGetRestTypeOfSignature +const MethodGetReturnTypeOfSignature = api.MethodGetReturnTypeOfSignature +const MethodGetSemanticDiagnostics = api.MethodGetSemanticDiagnostics +const MethodGetShorthandAssignmentValueSymbol = api.MethodGetShorthandAssignmentValueSymbol +const MethodGetSignaturesOfType = api.MethodGetSignaturesOfType +const MethodGetSourceFile = api.MethodGetSourceFile +const MethodGetStringType = api.MethodGetStringType +const MethodGetSuggestionDiagnostics = api.MethodGetSuggestionDiagnostics +const MethodGetSymbolAtLocation = api.MethodGetSymbolAtLocation +const MethodGetSymbolAtPosition = api.MethodGetSymbolAtPosition +const MethodGetSymbolOfType = api.MethodGetSymbolOfType +const MethodGetSymbolsAtLocations = api.MethodGetSymbolsAtLocations +const MethodGetSymbolsAtPositions = api.MethodGetSymbolsAtPositions +const MethodGetSyntacticDiagnostics = api.MethodGetSyntacticDiagnostics +const MethodGetTargetOfType = api.MethodGetTargetOfType +const MethodGetTypeArguments = api.MethodGetTypeArguments +const MethodGetTypeAtLocation = api.MethodGetTypeAtLocation +const MethodGetTypeAtLocations = api.MethodGetTypeAtLocations +const MethodGetTypeAtPosition = api.MethodGetTypeAtPosition +const MethodGetTypeFromTypeNode = api.MethodGetTypeFromTypeNode +const MethodGetTypeOfSymbol = api.MethodGetTypeOfSymbol +const MethodGetTypeOfSymbolAtLocation = api.MethodGetTypeOfSymbolAtLocation +const MethodGetTypeParametersOfType = api.MethodGetTypeParametersOfType +const MethodGetTypePredicateOfSignature = api.MethodGetTypePredicateOfSignature +const MethodGetTypesAtPositions = api.MethodGetTypesAtPositions +const MethodGetTypesOfSymbols = api.MethodGetTypesOfSymbols +const MethodGetTypesOfType = api.MethodGetTypesOfType +const MethodGetUndefinedType = api.MethodGetUndefinedType +const MethodGetUnknownType = api.MethodGetUnknownType +const MethodGetVoidType = api.MethodGetVoidType +const MethodGetWidenedType = api.MethodGetWidenedType +const MethodInitialize = api.MethodInitialize +const MethodIsArrayLikeType = api.MethodIsArrayLikeType +const MethodIsContextSensitive = api.MethodIsContextSensitive +const MethodParseConfigFile = api.MethodParseConfigFile +const MethodPrintNode = api.MethodPrintNode +const MethodRelease = api.MethodRelease +const MethodResolveName = api.MethodResolveName +const MethodSignatureToSignatureDeclaration = api.MethodSignatureToSignatureDeclaration +const MethodTypeToString = api.MethodTypeToString +const MethodTypeToTypeNode = api.MethodTypeToTypeNode +const MethodUpdateSnapshot = api.MethodUpdateSnapshot +//go:linkname NewAsyncConn github.com/microsoft/typescript-go/internal/api.NewAsyncConn +func NewAsyncConn(rwc io.ReadWriteCloser, handler api.Handler) *api.AsyncConn +//go:linkname NewAsyncConnWithProtocol github.com/microsoft/typescript-go/internal/api.NewAsyncConnWithProtocol +func NewAsyncConnWithProtocol(rwc io.ReadWriteCloser, protocol api.Protocol, handler api.Handler) *api.AsyncConn +//go:linkname NewDiagnosticResponse github.com/microsoft/typescript-go/internal/api.NewDiagnosticResponse +func NewDiagnosticResponse(d *ast.Diagnostic) *api.DiagnosticResponse +//go:linkname NewDiagnosticResponses github.com/microsoft/typescript-go/internal/api.NewDiagnosticResponses +func NewDiagnosticResponses(diags []*ast.Diagnostic) []*api.DiagnosticResponse +//go:linkname NewJSONRPCProtocol github.com/microsoft/typescript-go/internal/api.NewJSONRPCProtocol +func NewJSONRPCProtocol(rw io.ReadWriter) *api.JSONRPCProtocol +//go:linkname NewMessagePackProtocol github.com/microsoft/typescript-go/internal/api.NewMessagePackProtocol +func NewMessagePackProtocol(rw io.ReadWriter) *api.MessagePackProtocol +//go:linkname NewPipeTransport github.com/microsoft/typescript-go/internal/api.NewPipeTransport +func NewPipeTransport(path string) (*api.PipeTransport, error) +//go:linkname NewProjectResponse github.com/microsoft/typescript-go/internal/api.NewProjectResponse +func NewProjectResponse(p *project.Project) *api.ProjectResponse +//go:linkname NewSession github.com/microsoft/typescript-go/internal/api.NewSession +func NewSession(projectSession *project.Session, options *api.SessionOptions) *api.Session +//go:linkname NewStdioServer github.com/microsoft/typescript-go/internal/api.NewStdioServer +func NewStdioServer(options *api.StdioServerOptions) *api.StdioServer +//go:linkname NewStdioTransport github.com/microsoft/typescript-go/internal/api.NewStdioTransport +func NewStdioTransport(stdin io.ReadCloser, stdout io.WriteCloser) *api.StdioTransport +//go:linkname NewSymbolResponse github.com/microsoft/typescript-go/internal/api.NewSymbolResponse +func NewSymbolResponse(symbol *ast.Symbol) *api.SymbolResponse +//go:linkname NewSyncConn github.com/microsoft/typescript-go/internal/api.NewSyncConn +func NewSyncConn(rwc io.ReadWriteCloser, protocol api.Protocol, handler api.Handler) *api.SyncConn +//go:linkname NodeHandleFrom github.com/microsoft/typescript-go/internal/api.NodeHandleFrom +func NodeHandleFrom(node *ast.Node) api.Handle[ast.Node] +type ParseConfigFileParams = api.ParseConfigFileParams +type PipeTransport = api.PipeTransport +type PrintNodeParams = api.PrintNodeParams +type ProjectFileChanges = api.ProjectFileChanges +//go:linkname ProjectHandle github.com/microsoft/typescript-go/internal/api.ProjectHandle +func ProjectHandle(p *project.Project) api.Handle[project.Project] +type ProjectResponse = api.ProjectResponse +type Protocol = api.Protocol +type RawBinary = api.RawBinary +//go:linkname RegisterGetEffectDiagnosticsCallback github.com/microsoft/typescript-go/internal/api.RegisterGetEffectDiagnosticsCallback +func RegisterGetEffectDiagnosticsCallback(cb func(ctx context.Context, program *compiler.Program, c *checker.Checker, sourceFile *ast.SourceFile, options *etscore.EffectPluginOptions, rules []string) ([]*ast.Diagnostic, error)) +type ReleaseParams = api.ReleaseParams +type ResolveNameParams = api.ResolveNameParams +type Session = api.Session +type SessionOptions = api.SessionOptions +//go:linkname SignatureHandle github.com/microsoft/typescript-go/internal/api.SignatureHandle +func SignatureHandle(id uint64) api.Handle[checker.Signature] +type SignatureResponse = api.SignatureResponse +type SignatureToSignatureDeclarationParams = api.SignatureToSignatureDeclarationParams +type SnapshotChanges = api.SnapshotChanges +type SourceFileResponse = api.SourceFileResponse +type StdioServer = api.StdioServer +type StdioServerOptions = api.StdioServerOptions +type StdioTransport = api.StdioTransport +//go:linkname SymbolHandle github.com/microsoft/typescript-go/internal/api.SymbolHandle +func SymbolHandle(symbol *ast.Symbol) api.Handle[ast.Symbol] +type SymbolResponse = api.SymbolResponse +type SyncConn = api.SyncConn +type Transport = api.Transport +//go:linkname TypeHandle github.com/microsoft/typescript-go/internal/api.TypeHandle +func TypeHandle(t *checker.Type) api.Handle[checker.Type] +type TypePredicateResponse = api.TypePredicateResponse +type TypeResponse = api.TypeResponse +type TypeToTypeNodeParams = api.TypeToTypeNodeParams +type UpdateSnapshotParams = api.UpdateSnapshotParams +type UpdateSnapshotResponse = api.UpdateSnapshotResponse From b865d16bd36912b8684c344eba165f0639337f77 Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Wed, 13 May 2026 10:31:33 +0200 Subject: [PATCH 2/2] fix go.work for shim api --- go.work | 1 + 1 file changed, 1 insertion(+) diff --git a/go.work b/go.work index d4307a43..041fa093 100644 --- a/go.work +++ b/go.work @@ -3,6 +3,7 @@ go 1.26 use ( . ./etscore + ./shim/api ./shim/ast ./shim/astnav ./shim/bundled