Skip to content

Conversation

@pranavgaikwad
Copy link
Contributor

@pranavgaikwad pranavgaikwad commented Nov 25, 2025

  • Set up workspace folders correctly
  • Set up client capabilities so server enables support for document symbol hierarchy and definition links (required to find stuff in node_modules)
  • Disable workspace/symbol completely for nodejs (this is unreliable)
  • Add retries to definition and document symbol queries

Summary by CodeRabbit

  • Refactor

    • Improved initialization, workspace-folder normalization and deduplication, and open/close lifecycle handling for fewer duplicate events and safer concurrency.
    • Parallelized symbol caching and lookups with stronger concurrency controls and much more detailed logging.
  • New Features

    • Expanded client capabilities for richer code intelligence (definition link support, hierarchical document symbols, diagnostics refresh).
    • Initialization options still loaded from settings.
  • Tests

    • Added a test incident covering a React reference scenario.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

Normalizes Node.js workspace URIs, expands InitializeParams capabilities and WorkspaceFolders; updates LSP base client to track opened files, add mutexes, change symbol helper signatures to use uri.URI, adjust GetAllDeclarations to accept a workspace flag, and enhance symbol-querying, retries, and logging.

Changes

Cohort / File(s) Summary
Node.js service client
external-providers/generic-external-provider/pkg/server_configurations/nodejs/service_client.go
Normalize provided workspace location to file:// URIs; build deduplicated WorkspaceFolders for InitializeParams; expand ClientCapabilities (workspaceFolders, diagnostic refresh, richer text-document capabilities); preserve InitializationOptions; update EvaluateReferenced to pass new boolean to GetAllDeclarations.
LSP base service client (core)
lsp/base_service_client/base_service_client.go
Add openedFiles map + mutex and allConditionsMutex (RW); change SymbolSearchHelper.GetLanguageID to accept uri.URI; update GetAllDeclarations(ctx, query, useWorkspaceSymbol bool) to branch between workspace RPC vs symbol-cache; parallelize & strengthen populateDocumentSymbolCache; add retries and normalization in queryDocumentSymbol; robust unmarshalling in getDefinitionForPosition; update didOpen/didClose to use uri.URI; add verbose logging.
Base capabilities usage
lsp/base_service_client/base_capabilities.go
Update call site to GetAllDeclarations(ctx, query, true) to match new signature and pass workspace flag.
Symbol cache / search helper
lsp/base_service_client/symbol_cache.go
Change GetLanguageID signature to accept uri.URI, validate file scheme, derive language from u.Filename(), and add logging for discovered file counts.
Demo/test output
demo-output.yaml
Add incident entry for file:///examples/nodejs/Component.tsx noting a React reference found by the Node.js provider (lineNumber: 3).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BaseClient as LSPServiceClientBase
    participant Cache as SymbolCache
    participant Workspace as WorkspaceSymbolService

    rect rgb(245,250,245)
    Note over Client,BaseClient: Initialization + WorkspaceFolders normalization
    Client->>BaseClient: Initialize(WorkspaceFolders, Capabilities, InitializationOptions)
    BaseClient-->>Client: Initialized
    end

    rect rgb(250,245,245)
    Note over Client,BaseClient: File lifecycle & cache population
    Client->>BaseClient: NotifyFileChanges(files)
    activate BaseClient
    BaseClient->>BaseClient: Lock allConditionsMutex
    BaseClient->>BaseClient: didClose(changed URIs) (update openedFiles)
    BaseClient->>BaseClient: populateDocumentSymbolCache (parallel tasks)
    BaseClient->>BaseClient: Unlock allConditionsMutex
    deactivate BaseClient
    end

    rect rgb(245,245,250)
    Note over Client,BaseClient: Declaration lookup path (flag selects)
    Client->>BaseClient: GetAllDeclarations(ctx, query, useWorkspaceSymbol)
    alt useWorkspaceSymbol == true
        BaseClient->>Workspace: workspace/symbols RPC
        Workspace-->>BaseClient: []WorkspaceSymbol
    else
        BaseClient->>Cache: search cached DocumentSymbols
        Cache-->>BaseClient: []WorkspaceSymbol
    end
    BaseClient-->>Client: []WorkspaceSymbol
    end

    rect rgb(250,250,240)
    Note over Client,BaseClient: documentSymbol retrieval with retries
    Client->>BaseClient: queryDocumentSymbol(uri)
    BaseClient->>BaseClient: didOpen(uri) (track openedFiles)
    loop retries
        BaseClient->>BaseClient: request documentSymbol RPC
    end
    BaseClient-->>Client: Normalized DocumentSymbols
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect concurrency around openedFiles and allConditionsMutex.
  • Verify signature changes (GetLanguageID(uri.URI) and GetAllDeclarations(..., useWorkspaceSymbol)) and update all call sites.
  • Review retry/unmarshal logic in queryDocumentSymbol and getDefinitionForPosition.
  • Confirm URI normalization and workspace-folder deduplication in the Node.js service client.

Possibly related PRs

Suggested reviewers

  • tsanders-rh
  • shawn-hurley
  • eemcmullan

Poem

🐰 I nudged the folders into file:// light,

Symbols hop out, no longer lost in night,
Mutexes guard the warren's busy lanes,
Retries fluff errors until clarity remains,
A happy rabbit cheers: the index is bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of fixing issues with Node.js LSP queries by improving workspace folder setup, client capabilities, disabling workspace/symbol queries, and adding retries.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Signed-off-by: Pranav Gaikwad <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lsp/base_service_client/base_service_client.go (1)

661-690: Add mutex protection for openedFiles access.

The openedFiles map is read/written here without synchronization. This can cause data races when multiple goroutines call didOpen/didClose concurrently (e.g., during parallel symbol cache population).

 func (sc *LSPServiceClientBase) didOpen(ctx context.Context, uri uri.URI, text []byte) error {
+	sc.openedFilesMutex.Lock()
 	if _, exists := sc.openedFiles[uri]; exists {
+		sc.openedFilesMutex.Unlock()
 		return nil
 	}
 	sc.openedFiles[uri] = true
+	sc.openedFilesMutex.Unlock()
 	params := protocol.DidOpenTextDocumentParams{
 		// ...
 	}
 	return sc.Conn.Notify(ctx, "textDocument/didOpen", params)
 }

 func (sc *LSPServiceClientBase) didClose(ctx context.Context, uri uri.URI) error {
+	sc.openedFilesMutex.Lock()
 	if _, exists := sc.openedFiles[uri]; !exists {
+		sc.openedFilesMutex.Unlock()
 		return nil
 	}
 	delete(sc.openedFiles, uri)
+	sc.openedFilesMutex.Unlock()
 	params := protocol.DidCloseTextDocumentParams{
 		// ...
 	}
 	return sc.Conn.Notify(ctx, "textDocument/didClose", params)
 }
🧹 Nitpick comments (1)
external-providers/generic-external-provider/pkg/server_configurations/nodejs/service_client.go (1)

68-80: Consider using URI parsing instead of string replacement.

Line 77 uses strings.ReplaceAll(f, "file://", "") which could be fragile. If the path somehow contains file:// elsewhere (unlikely but possible), it would cause issues.

 		seen[f] = true
+		parsed, err := uri.Parse(f)
+		var name string
+		if err == nil {
+			name = filepath.Base(parsed.Filename())
+		} else {
+			name = filepath.Base(strings.TrimPrefix(f, "file://"))
+		}
 		workspaceFolders = append(workspaceFolders, protocol.WorkspaceFolder{
 			URI:  f,
-			Name: filepath.Base(strings.ReplaceAll(f, "file://", "")),
+			Name: name,
 		})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7ea23c and a4ca7e8.

📒 Files selected for processing (4)
  • external-providers/generic-external-provider/pkg/server_configurations/nodejs/service_client.go (4 hunks)
  • lsp/base_service_client/base_capabilities.go (1 hunks)
  • lsp/base_service_client/base_service_client.go (16 hunks)
  • lsp/base_service_client/symbol_cache.go (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-21T16:25:40.903Z
Learnt from: shawn-hurley
Repo: konveyor/analyzer-lsp PR: 860
File: lsp/base_service_client/base_service_client.go:212-214
Timestamp: 2025-10-21T16:25:40.903Z
Learning: In `lsp/base_service_client/base_service_client.go`, when an RPC connection is provided via `c.RPC` (not nil), it is treated as already initialized and no handler setup is performed. This is known technical debt that needs to be addressed in the future to ensure LSP notifications like `textDocument/publishDiagnostics` are properly processed.

Applied to files:

  • lsp/base_service_client/base_service_client.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test
  • GitHub Check: benchmark (windows-latest, windows)
  • GitHub Check: test (windows-latest, windows, amd64)
  • GitHub Check: test (macos-latest, darwin, arm64)
  • GitHub Check: test (macos-latest, darwin, amd64)
  • GitHub Check: test (ubuntu-latest, linux, arm64)
  • GitHub Check: test (ubuntu-latest, linux, amd64)
🔇 Additional comments (14)
lsp/base_service_client/base_capabilities.go (1)

57-57: LGTM!

The addition of true for useWorkspaceSymbol correctly enables workspace symbol queries for the generic EvaluateReferenced implementation, while allowing provider-specific overrides (like Node.js) to disable it.

lsp/base_service_client/symbol_cache.go (1)

200-215: LGTM!

The updated signature using uri.URI is more type-safe. The implementation correctly validates the file scheme before extracting the extension, returning an empty string for non-file URIs.

external-providers/generic-external-provider/pkg/server_configurations/nodejs/service_client.go (3)

49-55: LGTM!

The location normalization ensures consistent URI formatting. Setting the location as the sole workspace folder aligns with the PR objective.


82-97: LGTM!

The ClientCapabilities are correctly configured to:

  • Enable workspace folder support
  • Enable diagnostic refresh
  • Enable definition link support (required for node_modules lookups)
  • Enable hierarchical document symbols

These align with the PR objectives for proper Node.js LSP integration.


169-169: LGTM!

Passing false for useWorkspaceSymbol correctly disables the unreliable workspace/symbol queries for Node.js, per the PR objectives.

lsp/base_service_client/base_service_client.go (9)

341-343: LGTM!

Calling didClose before invalidating the cache ensures the LSP server is properly notified of file changes, preventing stale state.


353-355: LGTM!

The mutex properly protects the allConditions assignment from concurrent access during Prepare.


450-481: LGTM!

The useWorkspaceSymbol parameter cleanly controls whether to attempt workspace/symbol queries, falling back to the symbol cache. This enables provider-specific behavior (e.g., disabling for Node.js).


567-579: LGTM!

Creating a wsForDefinition with the matched symbol name ensures proper fallback when findDocumentSymbolAtLocation doesn't find a better match, preserving the definition location information.


706-741: LGTM!

The retry logic with maxAttempts=2 and 100ms delay addresses flaky document symbol queries. The normalization handling for workspace symbol types returned by typescript-language-server is a good defensive measure.


769-772: LGTM!

Proper use of RLock/RUnlock to safely read allConditions while allowing concurrent reads.


828-854: LGTM!

The unmarshalLocations helper robustly handles the various response formats from textDocument/definition:

  • []LocationLink (with TargetURI/TargetRange)
  • Single Location
  • []Location

This defensive parsing prevents failures with different LSP server implementations.


870-894: LGTM!

The retry logic for textDocument/definition mirrors the pattern used in queryDocumentSymbol, providing consistent retry behavior for flaky LSP operations.


899-944: LGTM!

The refactored findDocumentSymbolAtLocation now:

  1. Constructs a proper WorkspaceSymbol for comparison
  2. Uses MatchSymbolByPatterns for flexible matching
  3. Finds the smallest overlapping symbol (most specific match)

This provides more accurate symbol resolution.

Signed-off-by: Pranav Gaikwad <[email protected]>
Signed-off-by: Pranav Gaikwad <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
lsp/base_service_client/base_service_client.go (3)

278-280: Mutex unnecessary during initialization.

The mutex protection during map initialization is defensive but not required, as the struct has not yet been shared across goroutines at this point in NewLSPServiceClientBase.

Consider simplifying:

-	sc.openedFilesMutex.Lock()
 	sc.openedFiles = make(map[uri.URI]bool)
-	sc.openedFilesMutex.Unlock()

552-581: Add context cancellation check in parallel goroutines.

The parallelization with semaphore and WaitGroup is well-structured, but the goroutines should check ctx.Err() before performing expensive operations to respect cancellation.

Apply this diff to add context cancellation checks:

 			wg.Add(1)
 			go func(ms protocol.WorkspaceSymbol) {
 				defer wg.Done()
 				sem <- struct{}{}
 				defer func() { <-sem }()
 
+				if ctx.Err() != nil {
+					return
+				}
+
 				location, ok := ms.Location.Value.(protocol.Location)
 				if !ok {
 					sc.Log.V(7).Info("unable to get location from workspace symbol", "workspace symbol", ms)
 					return
 				}
 				defs := sc.getDefinitionForPosition(ctx, fileURI, location)
 				if len(defs) > 0 {
 					results <- defResult{matchedSymbol: ms, definitions: defs}
 				}
 			}(matchedSymbol)

955-979: Distinguish between RPC errors and empty responses.

The current implementation logs an error for both RPC failures and empty responses. Empty responses (no definitions found) are not necessarily errors and could reduce log noise.

Consider separating empty response handling:

 		for attempt := 1; attempt <= maxAttempts; attempt++ {
 			var tmp json.RawMessage
 			err := sc.Conn.Call(ctx, "textDocument/definition", position).Await(ctx, &tmp)
 			if err != nil {
 				lastErr = err
 			} else if len(tmp) == 0 {
-				lastErr = fmt.Errorf("textDocument/definition returned zero locations")
+				// Empty response is not an error, just no definitions found
+				return nil
 			} else {
 				locations, ok := unmarshalLocations(tmp)
 				if ok && len(locations) > 0 {
 					return locations
 				}
+				lastErr = fmt.Errorf("unable to unmarshal definition response")
 			}
 			if ctx.Err() != nil {
 				return nil
 			}
 			if attempt < maxAttempts {
 				time.Sleep(100 * time.Millisecond)
 			}
 		}
 		if lastErr != nil {
 			sc.Log.Error(lastErr, "textDocument/definition request failed", "uri", uri)
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4ca7e8 and c419d0b.

📒 Files selected for processing (3)
  • demo-output.yaml (1 hunks)
  • lsp/base_service_client/base_service_client.go (19 hunks)
  • lsp/base_service_client/symbol_cache.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lsp/base_service_client/symbol_cache.go
🧰 Additional context used
🧬 Code graph analysis (1)
lsp/base_service_client/base_service_client.go (1)
provider/provider.go (3)
  • ConditionsByCap (472-475)
  • Location (319-322)
  • Position (324-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: test
  • GitHub Check: test (ubuntu-latest, linux, arm64)
  • GitHub Check: test (ubuntu-latest, linux, amd64)
  • GitHub Check: test (macos-latest, darwin, arm64)
  • GitHub Check: test (macos-latest, darwin, amd64)
  • GitHub Check: test (windows-latest, windows, amd64)
  • GitHub Check: benchmark (macos-latest, mac)
  • GitHub Check: benchmark (ubuntu-latest, linux)
  • GitHub Check: benchmark (windows-latest, windows)
🔇 Additional comments (10)
demo-output.yaml (1)

806-811: New nodejs provider incident for tsx React reference aligns with PR fixes.

This addition shows the nodejs provider now detecting a React reference at line 3 (React.FC type annotation) in Component.tsx, complementing the existing detection at line 1 (React import). This reflects the improved nodejs provider capabilities for TypeScript/React files enabled by the PR changes (workspace setup, client capabilities, and query retries).

The incident structure and formatting are consistent with the existing patterns in the file.

lsp/base_service_client/base_service_client.go (9)

150-154: Mutexes added correctly to protect concurrent access.

The addition of openedFilesMutex (regular mutex) and allConditionsMutex (RW mutex) properly addresses the concurrency concerns flagged in the previous review. The choice of mutex types is appropriate: openedFilesMutex for simple map operations and allConditionsMutex for read-heavy access patterns in parallel goroutines.


344-346: Proper lifecycle handling for file changes.

Calling didClose when files change ensures proper LSP lifecycle management and signals the server to release resources for the old version before re-querying.


356-358: Correct mutex usage for concurrent write protection.

The allConditionsMutex properly protects the write to allConditions, preventing data races with concurrent readers in parallel goroutines.


699-734: Proper tracking of opened files to prevent duplicate notifications.

The implementation correctly uses openedFilesMutex to track opened files and avoid duplicate didOpen/didClose notifications. The pattern of checking state before RPC calls is appropriate.


764-772: Verify mixed response type handling.

The normalization assumes all symbols in the response have the same shape (either all with Location or all without). If the server returns mixed types, symbols without Location will have unset Range fields, which could cause issues downstream.

Consider adding validation:

 		} else {
 			documentSymbols := make([]protocol.DocumentSymbol, 0, len(symbols))
 			// typescript-language-server seems to return workspaceSymbol types even for document symbols
 			// we need to normalize them back into document symbol types by copying the range
 			for _, symbol := range symbols {
 				if symbol.Location != nil {
 					symbol.DocumentSymbol.Range = symbol.Location.Range
+				} else if symbol.DocumentSymbol.Range.Start.Line == 0 && symbol.DocumentSymbol.Range.End.Line == 0 {
+					sc.Log.V(7).Info("symbol missing both Location and Range", "symbol", symbol.Name)
+					continue
 				}
 				documentSymbols = append(documentSymbols, symbol.DocumentSymbol)
 			}

984-1029: Enhanced symbol matching with name pattern validation.

The updated logic now combines range overlap checking with name pattern matching (via MatchSymbolByPatterns), providing more robust symbol identification. This reduces false positives when multiple symbols overlap the same location.


453-486: Unable to verify call sites due to repository access issues.

The repository clone is failing, preventing verification of all GetAllDeclarations call sites. While the breaking signature change is clear (addition of useWorkspaceSymbol bool parameter), I cannot confirm that all callers have been updated to provide this new parameter.


504-506: Since I'm unable to access the repository to verify the callers, I'll provide a rewritten review comment based on the concern raised:

Verify nil return value is properly handled by all callers of GetAllReferences.

This function now returns nil on RPC failure (line 505) instead of an empty slice. Callers must check for nil to prevent nil pointer dereference. Confirm all call sites handle this return value appropriately.


174-174: Verify all GetLanguageID implementations have been updated to use uri.URI parameter.

The GetLanguageID method signature changed from accepting string to uri.URI. This is a breaking interface change for all implementations of the SymbolSearchHelper interface. Confirm that all implementations throughout the codebase have been updated to use the new uri.URI parameter type.

Signed-off-by: Pranav Gaikwad <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
lsp/base_service_client/base_service_client.go (6)

276-281: Initialization of openedFiles under mutex is fine but mutex isn’t strictly needed here

You correctly allocate openedFiles before any goroutines are started; the extra lock/unlock is harmless but not necessary at construction time. Optional micro-cleanup only.


355-367: All-conditions guarded by RWMutex; WaitGroup usage looks mostly sound

Locking allConditions during Prepare and reading it under RLock in the workers avoids races. The symbolCacheUpdateWaitGroup pattern (Add before enqueue; Done in processSymbolCacheUpdate and in the URI producer goroutine) appears balanced.

One subtlety: GetAllDeclarations can call Wait while other goroutines call Add (e.g., via NotifyFileChanges). If callers ever fire NotifyFileChanges after they start querying, Wait might return before those late additions complete. If that ordering matters for your use case, you may want a higher-level contract or a separate “prepare done” barrier.


736-786: queryDocumentSymbol: retries and normalization are good; silent nil result may hide persistent failures

Positives:

  • didOpen before requesting symbols helps temperamental servers.
  • Two attempts with a small delay should reduce flakiness.
  • Normalizing TS servers that return workspace-symbol-shaped payloads into pure DocumentSymbol and caching them is pragmatic.

Concern:

  • On repeated failures or zero-symbol responses, the function returns (nil, nil) and only logs at V(7). Callers can’t distinguish “no symbols” from “RPC or decode failed,” which might make debugging harder and changes prior error semantics.

Consider returning the last error when all attempts fail:

-    sc.Log.V(7).Info("textDocument/documentSymbol failed", "uri", uri, "error", lastErr)
-    return nil, nil
+    sc.Log.V(7).Info("textDocument/documentSymbol failed", "uri", uri, "error", lastErr)
+    return nil, lastErr

and adjusting callers that intentionally want to ignore the error (e.g., treat it as “no symbols”) to do so explicitly.


805-911: Parallel line scanning and guarded access to shared state look safe

The new implementation of searchContentForWorkspaceSymbols:

  • Scans lines in parallel with a bounded semaphore (20) and uses allConditionsMutex.RLock to safely read shared conditions.
  • Calls queryDocumentSymbol at most once per file (per search) and guards the cached symbols slice with symbolsMutex.
  • Fixes the earlier early-return bug on filepath.Abs by logging and continuing, so a single bad path won’t drop all matches.

Overall the function is more scalable and robust. If you ever want to be stricter about cancellation, you could also check ctx.Err() inside the per-line goroutines before doing heavier work, but that’s optional.


913-983: getDefinitionForPosition: robust response-shape handling and retries; same comment on silent nil

The unmarshalLocations helper correctly covers the LSP variants for definition responses (LocationLink[], Location, Location[]) and normalizes them into a []protocol.Location. Combined with:

  • didOpen + file read before the request,
  • two attempts with a small delay,
  • and logging on final failure,

this should improve reliability against both Node/TS and other servers.

As with queryDocumentSymbol, though, the function returns nil without an error in all failure modes except capability-missing, which blurs “no definition” vs “request failed repeatedly.” If callers ever need to differentiate those, consider returning lastErr alongside a nil slice.


985-1030: findDocumentSymbolAtLocation matches definitions to symbols using overlap + pattern and prefers the smallest range

This traversal combines:

  • Range overlap against the definition location,
  • Language-specific MatchSymbolByPatterns on the symbol name/container,
  • And a “shortest range wins” heuristic,

which is a sensible approach to pick the innermost matching symbol. Type assertions on defSymbol.Location.Value are safe given current call sites, and using docURI for the workspace symbol location keeps URIs consistent.

Just document the “shortest enclosing range” behavior somewhere if external consumers depend on which symbol is chosen when multiple overlap.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c419d0b and e996d3a.

📒 Files selected for processing (1)
  • lsp/base_service_client/base_service_client.go (19 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: pranavgaikwad
Repo: konveyor/analyzer-lsp PR: 975
File: external-providers/generic-external-provider/pkg/server_configurations/nodejs/symbol_cache_helper.go:29-78
Timestamp: 2025-11-21T14:12:52.228Z
Learning: In the analyzer-lsp codebase, additional workspace folders (additionalPaths) used in FileSearcher are already vetted and scoped, so they do not require additional exclusion patterns for directories like node_modules, .git, dist, etc. Only the primary path needs these exclusions.
🧬 Code graph analysis (1)
lsp/base_service_client/base_service_client.go (3)
lsp/protocol/tsprotocol.go (15)
  • URI (4673-4673)
  • WorkspaceSymbol (5052-5063)
  • Info (5477-5477)
  • Location (2389-2392)
  • Range (3559-3564)
  • OrPLocation_workspace_symbol (2764-2766)
  • BaseSymbolInformation (51-65)
  • DidOpenTextDocumentParams (1100-1103)
  • TextDocumentItem (4485-4495)
  • DidCloseTextDocumentParams (1083-1086)
  • TextDocumentIdentifier (4478-4481)
  • Text (5341-5341)
  • File (5582-5582)
  • Position (3452-3466)
  • DocumentURI (1460-1460)
provider/provider.go (3)
  • ConditionsByCap (472-475)
  • Location (319-322)
  • Position (324-342)
lsp/base_service_client/symbol_cache.go (1)
  • WorkspaceSymbolDefinitionsPair (27-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: test
  • GitHub Check: benchmark (macos-latest, mac)
  • GitHub Check: benchmark (windows-latest, windows)
  • GitHub Check: benchmark (ubuntu-latest, linux)
  • GitHub Check: test (windows-latest, windows, amd64)
  • GitHub Check: test (macos-latest, darwin, amd64)
  • GitHub Check: test (macos-latest, darwin, arm64)
  • GitHub Check: test (ubuntu-latest, linux, arm64)
  • GitHub Check: test (ubuntu-latest, linux, amd64)
🔇 Additional comments (7)
lsp/base_service_client/base_service_client.go (7)

150-155: Good: shared state for opened files and conditions now mutex-protected

Adding openedFilesMutex and allConditionsMutex around shared maps/slices addresses prior race concerns and makes concurrent access from cache-population goroutines safe.


330-350: Closing documents on file change is reasonable; relies on consistent uri mapping

Calling didClose before invalidating and re-populating the symbol cache should help TS/Node servers reset state for modified files. This assumes every URI passed here matches what was used in didOpen (via toURI), otherwise the close will be skipped silently by the openedFiles map. That’s probably fine, but worth keeping in mind if you later introduce alternative URI construction paths.


523-528: Symbol cache population: concurrency, keying, and fallbacks look correct

The new workspaceSymbolKey and parallel definition lookups (bounded by a semaphore of 10) are a solid improvement:

  • Type assertions on WorkspaceSymbol.Location.Value are only applied to symbols constructed in this function, so they’re safe as long as future callers keep that invariant.
  • The results channel + wg pattern avoids goroutine leaks and processes only symbols with actual definitions.
  • When findDocumentSymbolAtLocation can’t improve a definition, you correctly fall back to the synthetic workspace symbol.

All of this should make cache population faster and more resilient without obviously changing semantics.

Also applies to: 535-551, 553-582, 583-642


168-188: I'll help you verify that all implementations of SymbolSearchHelper.GetLanguageID were updated to accept the uri.URI parameter. Let me search the codebase for all implementations.
<function_calls>


#!/bin/bash

Search for all GetLanguageID method implementations in Go files

rg -n "GetLanguageID(" --type go -A 2 -B 2


</function_calls>


#!/bin/bash

Also search for types that might implement SymbolSearchHelper

rg -n "type.*SymbolSearchHelper" --type go -A 5 -B 2


</function_calls>

<function_calls>


#!/bin/bash

Find all receiver types that have GetLanguageID method

ast-grep --pattern $'func ($_ $_ ) GetLanguageID( $$$ ) {
$$$
}'


</function_calls>

Let me also find the SymbolSearchHelper interface definition and check the broader context:
<function_calls>


#!/bin/bash

Find the SymbolSearchHelper interface definition

rg -n "type SymbolSearchHelper" --type go -A 30


</function_calls>


699-734: I'm unable to verify the review comment at this time due to persistent repository access issues. However, let me provide the rewritten review comment based on the limitation:


didOpen/didClose with openedFiles dedupe align better with LSP expectations, but symbolSearchHelper nil-safety requires verification

Tracking opened URIs and short-circuiting duplicate opens/closes should reduce noisy LSP notifications and avoid TS "No project" issues. Using uri.URI everywhere for the key keeps it consistent with toURI.

⚠️ Verification needed: The code calls sc.symbolSearchHelper.GetLanguageID(uri) in didOpen without nil checks. Confirm that symbolSearchHelper is always initialized in NewLSPServiceClientBase and never set to nil after initialization, otherwise this will panic at runtime.


453-487: Yes, please search for both:

  1. LSPServiceClientBase.GetAllDeclarations - to find all call sites and see what boolean values are being passed for the useWorkspaceSymbol parameter
  2. useWorkspaceSymbol - to understand the usage patterns and see if there are any inconsistencies

This will help verify whether callers are correctly passing the flag as the review comment suggests.


489-508: Based on my research, I can now provide a complete verification of the review comment.

Key findings:

  1. LSP Protocol Contract: The LSP specification explicitly allows Location[] | null for the textDocument/references response, meaning returning null (which nil marshals to) is protocol-compliant.

  2. Go Idiom: According to Go best practices, when a function encounters an error, returning nil for a slice (the zero value) is idiomatic and standard. This follows the pattern: "on error, return nil, err."

  3. JSON Encoding: In Go's encoding/json:

    • nil slice → JSON null
    • []T{} → JSON []

    Both are valid per the LSP spec.

  4. Caller Impact: The concern about callers checking == nil vs len(...) == 0 is valid but minor:

    • range and len() work identically for both nil and empty slices
    • Only explicit == nil checks or JSON marshalling behavior would differ
    • This is a real behavior change if the previous code returned []protocol.Location{}

Conclusion: The reviewer's concern is valid but nuanced. The change follows Go idioms and LSP protocol specs. However, without verifying the previous implementation, we cannot definitively say if this is an oversight or intentional refactoring. The suggested fix (returning empty slice) would also be acceptable and would preserve prior semantics if callers were checking for nil.


Behavior change: on error, GetAllReferences now returns nil instead of an empty slice

Logging the failure is good. Returning nil aligns with Go idioms (nil being the zero value for error cases) and is valid per the LSP specification, which allows Location[] | null. However, this is a behavioral change: callers explicitly checking == nil will see different results than before. If prior behavior returned []protocol.Location{}, consider preserving semantics for compatibility:

-    sc.Log.Error(err, "textDocument/references request failed", "uri", location.URI)
-    return nil
+    sc.Log.Error(err, "textDocument/references request failed", "uri", location.URI)
+    return []protocol.Location{}

Copy link
Contributor

@shawn-hurley shawn-hurley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits can be follow one if this helps get ci stable

}
params.WorkspaceFolders = workspaceFolders

params.Capabilities = protocol.ClientCapabilities{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either here or a follow up can note what each of these does and why we need them?

".go": "go",
}
return languageIDMap[filepath.Ext(filepath.Base(uri))]
if strings.HasPrefix(string(u), fmt.Sprintf("%s://", uri.FileScheme)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use the same constant above?

Signed-off-by: Pranav Gaikwad <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
external-providers/generic-external-provider/pkg/server_configurations/nodejs/service_client.go (3)

49-55: Location → workspace folder normalization looks correct; consider more robust URI construction.

Treating c.Location as the single workspace folder and ensuring a file:// prefix aligns with the PR goal. The manual prefixing assumes c.Location is a filesystem path; for cross‑platform robustness you might instead resolve to an absolute path and construct the URI via uri.File (and optionally handle errors):

if c.Location != "" {
    if !strings.HasPrefix(c.Location, "file://") {
        abs, err := filepath.Abs(c.Location)
        if err != nil {
            return nil, fmt.Errorf("resolving location %q: %w", c.Location, err)
        }
        c.Location = uri.File(abs).String()
    }
    sc.Config.WorkspaceFolders = []string{c.Location}
}

Also worth double‑checking that overwriting any YAML‑provided WorkspaceFolders when Location is set is the intended behavior for Node.


68-81: WorkspaceFolders construction/dedup is solid; name derivation could lean on URI helpers.

The deduplication map and conversion into protocol.WorkspaceFolder look good and should avoid redundant folders. For the Name field, instead of manually stripping "file://" and then calling filepath.Base, you could derive the path via the URI helper for clarity and fewer string assumptions, e.g.:

u, err := uri.Parse(f)
if err == nil {
    name := filepath.Base(u.Filename())
    workspaceFolders = append(workspaceFolders, protocol.WorkspaceFolder{
        URI:  f,
        Name: name,
    })
} else {
    // fallback to current behavior or log
}

This keeps the logic aligned with how the rest of the code parses URIs and is friendlier to any future non‑file schemes.


176-178: New GetAllDeclarations flag: confirm semantics and consider making intent self‑documenting.

Passing false for the new GetAllDeclarations boolean aligns with the PR intent to avoid flaky workspace‑wide symbol queries for Node, but the meaning of the flag isn’t obvious at the call site. Two follow‑ups to consider:

  • Confirm that false indeed selects the “no workspace/symbol (or no references)” path you want for Node.
  • To improve readability, consider a named constant or options struct so the call reads more like GetAllDeclarations(ctx, query, useWorkspaceSymbol /* false */) or similar, instead of a bare boolean.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e996d3a and 1bc343e.

📒 Files selected for processing (1)
  • external-providers/generic-external-provider/pkg/server_configurations/nodejs/service_client.go (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: pranavgaikwad
Repo: konveyor/analyzer-lsp PR: 975
File: external-providers/generic-external-provider/pkg/server_configurations/nodejs/symbol_cache_helper.go:29-78
Timestamp: 2025-11-21T14:12:52.228Z
Learning: In the analyzer-lsp codebase, additional workspace folders (additionalPaths) used in FileSearcher are already vetted and scoped, so they do not require additional exclusion patterns for directories like node_modules, .git, dist, etc. Only the primary path needs these exclusions.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: test
  • GitHub Check: test (macos-latest, darwin, arm64)
  • GitHub Check: test (windows-latest, windows, amd64)
  • GitHub Check: test (macos-latest, darwin, amd64)
  • GitHub Check: test (ubuntu-latest, linux, amd64)
  • GitHub Check: test (ubuntu-latest, linux, arm64)
  • GitHub Check: benchmark (windows-latest, windows)
  • GitHub Check: benchmark (macos-latest, mac)
  • GitHub Check: benchmark (ubuntu-latest, linux)
🔇 Additional comments (2)
external-providers/generic-external-provider/pkg/server_configurations/nodejs/service_client.go (2)

7-7: Import of path/filepath is appropriate and scoped.

The new filepath import is used only for deriving workspace folder names and is a reasonable choice; no issues here.


82-105: Capabilities block matches stated needs; verify behavior against the Node language server.

Enabling workspace folders, diagnostics refresh, definition LinkSupport, and hierarchical document symbols is consistent with the PR description and should give you richer symbol info (and LocationLinks) needed for reliable Node/TS analysis.

Since this is tightly coupled to LSP server behavior, please verify via integration/E2E that:

  • The Node language server returns LocationLink[] for textDocument/definition given LinkSupport: true.
  • textDocument/documentSymbol returns hierarchical symbols as expected.
  • You are not accidentally re‑enabling workspace/symbol for Node (i.e., no WorkspaceSymbol capability advertised here or elsewhere for this client).

@pranavgaikwad pranavgaikwad merged commit 3d1bb23 into konveyor:main Nov 26, 2025
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants