From 34e99288261781ba870351fddccb4d8339b5f5de Mon Sep 17 00:00:00 2001 From: David Burg Date: Tue, 12 May 2026 23:13:54 -0700 Subject: [PATCH 1/7] docs: Add architecture documentation (#60) --- Docs/architecture.md | 281 +++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 Docs/architecture.md diff --git a/Docs/architecture.md b/Docs/architecture.md new file mode 100644 index 0000000..c7b26f3 --- /dev/null +++ b/Docs/architecture.md @@ -0,0 +1,281 @@ +# Architecture Overview + +This document describes the architecture of the Connectors SDK LSP server and VS Code extension. It is intended for developers contributing to or extending the project. + +## Component Map + +The system has two main parts: a **VS Code extension** (TypeScript) that manages lifecycle and configuration, and an **LSP server** (C#/.NET 8) that performs code analysis. + +### VS Code Extension (`vscode-extension/src/extension.ts`) + +The extension is the entry point. On activation it: + +1. **Scans for SDK references** — reads `.csproj` files to check for `PackageReference` to the Connector SDK. If no reference is found and no explicit `lspServerPath` is configured, the extension stays inactive. +2. **Resolves the SDK path** — a 4-step fallback chain (see [SDK Discovery Chain](#adr-4-sdk-discovery-chain) below). +3. **Resolves the server DLL** — checks explicit setting → sibling debug build → bundled `server/` directory. +4. **Builds `initializationOptions`** — merges API config, connections (from `connections.json` and `local.settings.json`), CodeLens config, and telemetry config into a single JSON payload. +5. **Starts the LSP server** via `dotnet --sdk ` over stdio transport. +6. **Sets up file watchers** — watches `connections.json` and `local.settings.json` for changes; sends merged updates to the server via the `custom/updateConnections` notification. +7. **Starts a token refresh loop** — periodically acquires Azure tokens and pushes them to the server via `custom/updateApiConfig`. +8. **Registers commands** — `restartLanguageServer`, `openConnectionView`, `sdklsp.applyEdits` (for click-to-insert links in hover tooltips). + +### LSP Server + +#### Server Core + +| File | Responsibility | +|------|----------------| +| `Program.cs` | Entry point. Parses CLI args (`--sdk`, `--sdk-assembly`), creates `SdkIndex`, registers all services and handlers via DI (`Microsoft.Extensions.DependencyInjection`), wires custom notification handlers (`custom/updateApiConfig`, `custom/updateConnections`), parses `initializationOptions` on `OnInitialize`. | +| `SdkIndex.cs` | Eagerly indexes SDK assemblies at startup via `System.Reflection.Metadata`. Exposes immutable collections: `ConnectorNameConstants`, `TriggerOperationsByConnector`, `TypeNames`, and a `FrozenSet` lookup for O(1) type existence checks. Created from `.nupkg` (via `NupkgLoader`) or directly from DLL paths. | +| `BufferManager.cs` | Thread-safe `ConcurrentDictionary` mapping document URIs to their latest text content. Updated on every `didOpen`/`didChange` event. Provides `GetAllBuffers()` for re-triggering diagnostics when external state changes. | +| `NupkgLoader.cs` | Extracts `.nupkg` (ZIP) via SharpZipLib and returns discovered DLL paths. | + +#### Handlers (LSP Protocol Endpoints) + +| Handler | Protocol Method | What It Does | +|---------|----------------|--------------| +| `TextDocumentSyncHandler` | `didOpen`, `didChange`, `didSave`, `didClose` | Updates `BufferManager`, invalidates `CompilationService` cache, schedules debounced diagnostics via `DiagnosticPublisher`, requests CodeLens refresh when SDK usage is detected. | +| `HoverHandler` | `textDocument/hover` | Parses the document, gets a `SemanticModel` from `CompilationService`, resolves the symbol at the cursor position, and returns rich Markdown hover content. For `[DynamicValues]` parameters, calls `DynamicOperationsRegistry` → `ApiService` to fetch live values from the connector API. | +| `CompletionHandler` | `textDocument/completion` | Detects attribute argument contexts (connector names, operation IDs, trigger operations) and returns completion items from `SdkIndex`. For dynamic value parameters, fetches suggestions via `ApiService`. | +| `CodeLensHandler` | `textDocument/codeLens` | Scans method signatures for SDK return types and attributes, and renders inline metadata (connector name, operation, HTTP method) above method declarations. Command names are configurable via `CodeLensConfig`. | +| `DynamicSchemaCodeActionHandler` | `textDocument/codeAction` | Detects `[DynamicSchema]` types at the cursor position using a pre-built cache of type→operationId mappings (built from SDK metadata at construction time). Returns a lightbulb code action — no schema fetch happens here. | +| `GenerateDynamicSchemaCommandHandler` | `workspace/executeCommand` | Executes when the user clicks the DynamicSchema code action. Fetches the JSON schema from the connector API, generates a typed C# class via `SchemaToClassGenerator`, and applies the edit. | + +#### Services + +| Service | Responsibility | +|---------|----------------| +| `CompilationService` | Creates and caches Roslyn `CSharpCompilation` instances per document URI. Cache key is `(textLength, contentChecksum, projectDirectory)`. References include .NET core assemblies, SDK assemblies (from `SdkIndex`), and project NuGet references (discovered from `project.assets.json`). Also provides `CreateSdkMetadataCompilation()` for metadata-only analysis. | +| `ApiService` | Makes authenticated HTTP calls to the Azure API Hub for dynamic values and schema resolution. Uses `DefaultAzureCredential` (Azure CLI) or an explicit bearer token. Responses are cached via `FusionCache` with 5-minute TTL and fail-safe (serve stale data for up to 1 hour on API failure). | +| `ConnectionsService` | Thread-safe store for the current connections configuration (managed API + DirectClient). Updated at initialization and at runtime via `custom/updateConnections` notifications. | +| `CodeLensConfig` | Holds configurable CodeLens command names, populated from `initializationOptions`. | +| `ITelemetryService` / `TelemetryService` | Application Insights integration with debug mode. Tracks events, metrics, exceptions, and dependency calls. Initialized from `initializationOptions.telemetry`. | + +#### Diagnostics Pipeline + +``` +TextDocumentSyncHandler + │ (on didOpen / didChange / didSave) + ▼ +DiagnosticPublisher + │ Debounces changes (500ms default) + │ Runs all IDiagnosticValidator implementations + │ Aggregates results + ▼ +textDocument/publishDiagnostics → VS Code +``` + +`DiagnosticPublisher` iterates over all registered `IDiagnosticValidator` instances and publishes the combined diagnostics. When connections change at runtime, the publisher re-validates all open documents. + +**Registered validators** (in registration order from `Program.ConfigureServices`): + +| Validator | What It Checks | +|-----------|----------------| +| `SdkUsageValidator` | General SDK usage patterns | +| `AttributeValidator` | Attribute argument validity (e.g., connector names, operation IDs against `SdkIndex`) | +| `ConnectionConfigValidator` | Whether required connections are configured for used connectors | +| `TriggerPayloadValidator` | Trigger payload type conventions | +| `DynamicValuesValidator` | `[DynamicValues]` parameter values against live API data | +| `SdkAntiPatternValidator` | Common anti-patterns in SDK usage | + +See [`.github/copilot-instructions.md`](.github/copilot-instructions.md#architecture-diagnostic-validators) for validator authoring guidelines, including the analysis strategy and key implementation details. + +#### State Management (`Store/`) + +`LSPStore` is a centralized state container (Redux-inspired) with three slices: + +| Slice | Backing Store | Purpose | +|-------|---------------|---------| +| `DynamicDataStore` | `ConcurrentDictionary` | Caches dynamic values fetched from APIs, keyed by `{connector}:{operation}:{connectionName}`. Shared across Hover and Completion handlers to avoid duplicate API calls. 5-minute TTL. | +| `DocumentDataSlice` | `ConcurrentDictionary` | Per-document metadata (e.g., detected SDK usage patterns). | +| `SessionDataSlice` | `ConcurrentDictionary` | Generic key-value store for session-scoped data. | + +## Data Flow Diagrams + +### Startup + +```mermaid +sequenceDiagram + participant VSCode as VS Code Extension + participant Server as LSP Server (Program.cs) + participant Index as SdkIndex + + VSCode->>VSCode: Scan .csproj for SDK PackageReference + VSCode->>VSCode: Resolve SDK path (settings → assets → SDK/ → sibling repo) + VSCode->>VSCode: Resolve server DLL path + VSCode->>VSCode: Build initializationOptions (API config, connections, telemetry) + VSCode->>Server: Start process: dotnet SdkLspServer.dll --sdk + Server->>Index: SdkIndex.TryCreateAsync(nupkgPath) or TryCreateFromAssembliesAsync(dllPaths) + Index-->>Server: SdkIndex (assemblies, types, connectorNames, triggerOperations) + Server->>Server: Register DI services, handlers, validators + VSCode->>Server: initialize (with initializationOptions) + Server->>Server: Parse apiConfig, connections, codeLens, telemetry from initializationOptions + Server-->>VSCode: initialized (capabilities) + VSCode->>VSCode: Setup file watchers (connections.json, local.settings.json) + VSCode->>VSCode: Start token refresh loop +``` + +### Document Edit → Diagnostics + +```mermaid +sequenceDiagram + participant Editor as VS Code Editor + participant Sync as TextDocumentSyncHandler + participant BM as BufferManager + participant DP as DiagnosticPublisher + participant V as Validators (6x) + + Editor->>Sync: didChange (document text) + Sync->>BM: UpdateBuffer(uri, text) + Sync->>DP: ScheduleDebouncedPublish(uri, text) + Note over DP: 500ms debounce + DP->>V: ValidateAsync(uri, text, sdkIndex) × 6 validators + V-->>DP: List (aggregated) + DP->>Editor: textDocument/publishDiagnostics +``` + +### Hover + +```mermaid +sequenceDiagram + participant Editor as VS Code + participant HH as HoverHandler + participant BM as BufferManager + participant CS as CompilationService + participant SDK as SdkIndex + participant API as ApiService + + Editor->>HH: textDocument/hover (position) + HH->>BM: GetBuffer(uri) + HH->>CS: GetCompilation(uri, syntaxTree) + CS-->>HH: (Compilation, SemanticModel) + HH->>HH: Find symbol at position via SemanticModel + alt Symbol has [DynamicValues] attribute + HH->>API: Fetch dynamic values (connector, operation, connection) + API-->>HH: List of values + HH->>HH: Build hover with value list + else Regular SDK symbol + HH->>SDK: Look up type/operation metadata + SDK-->>HH: Connector/operation info + HH->>HH: Build hover with documentation + end + HH-->>Editor: Hover (Markdown content) +``` + +### Completion + +```mermaid +sequenceDiagram + participant Editor as VS Code + participant CH as CompletionHandler + participant CS as CompilationService + participant SDK as SdkIndex + participant API as ApiService + + Editor->>CH: textDocument/completion (position) + CH->>CH: Parse document, find token at cursor + CH->>CH: Detect attribute context (ConnectorName, OperationId, TriggerOperation) + alt Connector name context + CH->>SDK: Get ConnectorNameConstants + SDK-->>CH: List of connector names + else Operation ID context + CH->>SDK: Get operations for connector + SDK-->>CH: List of operation IDs + else Dynamic value context + CH->>CS: GetCompilation → resolve [DynamicValues] attribute + CH->>API: Fetch dynamic values + API-->>CH: Value suggestions + end + CH-->>Editor: CompletionList +``` + +### Dynamic Values Resolution + +```mermaid +sequenceDiagram + participant HH as HoverHandler + participant Reg as DynamicOperationsRegistry + participant Disc as SdkDynamicOperationsDiscovery + participant CS as CompilationService + participant API as ApiService + participant Cache as FusionCache + + HH->>Reg: GetOperationMetadata(connector, operation) + alt First call (not yet discovered) + Reg->>Disc: DiscoverOperations(sdkIndex, compilationService) + Disc->>CS: CreateSdkMetadataCompilation() + Disc->>Disc: Analyze SDK types via reflection metadata + Disc-->>Reg: Dictionary + end + Reg-->>HH: DynamicOperationMetadata (API path, HTTP method) + HH->>API: GET /api/connectors/{connector}/{operation} + API->>Cache: Check cache (5min TTL, 1hr fail-safe) + alt Cache miss + API->>API: HTTP call with bearer token + API->>Cache: Store response + end + API-->>HH: Dynamic values +``` + +## Analysis Strategy + +The LSP server uses two levels of code analysis depending on the latency budget: + +- **Syntax-only analysis** — Used by diagnostic validators and fast-path handlers. Parses the document with `CSharpSyntaxTree.ParseText()` and walks the syntax tree without building a full compilation. This avoids the cost of reference resolution and provides sub-100ms response times. +- **Semantic model analysis** — Used by Hover, Completion, and CodeLens handlers when they need type resolution, symbol lookup, or attribute inspection. Goes through `CompilationService.GetCompilation()` to get a full Roslyn `SemanticModel` with SDK and NuGet references. + +For detailed validator authoring guidelines covering this strategy, see the [Architecture: Diagnostic Validators](.github/copilot-instructions.md#architecture-diagnostic-validators) section in `.github/copilot-instructions.md`. + +## Key Design Decisions + +### ADR 1: MetadataReferences Instead of Assembly.Load + +**Context:** The LSP server needs to analyze SDK types and their attributes without executing SDK code. + +**Decision:** Use Roslyn `MetadataReference.CreateFromFile()` to load SDK assemblies as metadata references for compilation, rather than `Assembly.Load()` or `Assembly.LoadFrom()`. + +**Rationale:** +- Avoids dependency loading issues — `Assembly.Load` pulls in transitive dependencies that may conflict with the server's own dependencies +- No code execution risk — metadata references are read-only views of the assembly's type system +- Compatible with Roslyn's `CSharpCompilation` — the same references are used for semantic analysis +- `SdkIndex` uses `System.Reflection.Metadata` (raw IL reading) for initial indexing, which is even lighter + +### ADR 2: Singleton CompilationService with Caching + +**Context:** Multiple handlers (Hover, Completion, CodeLens, Diagnostics) need Roslyn compilations for the same document. + +**Decision:** A single `CompilationService` instance caches one compilation per document URI. The cache key is `(textLength, contentChecksum, projectDirectory)` — a new compilation is created only when the document text changes or a different project context is detected. + +**Rationale:** +- Avoids redundant compilation creation across handlers processing the same document version +- The `ConcurrentDictionary` cache retains only the latest version per URI to bound memory +- When the caller passes a different `SyntaxTree` instance with identical text, the service replaces the tree in the cached compilation via `ReplaceSyntaxTree` (cheap operation) rather than rebuilding from scratch + +### ADR 3: Syntax-Only Diagnostics + +**Context:** Diagnostic validators run on every keystroke (debounced to 500ms). Full semantic analysis on each change would introduce noticeable latency. + +**Decision:** Diagnostic validators primarily use syntax-tree walking. Semantic model usage is reserved for specific checks that cannot be done syntactically (e.g., resolving `[DynamicValues]` attribute arguments to their declared operations). + +**Rationale:** +- Syntax parsing is ~1ms for typical files vs ~50-100ms for full compilation +- Most SDK diagnostics (attribute presence, connector name validation, anti-pattern detection) can be checked syntactically by matching attribute names and string literal arguments against `SdkIndex` +- Validators that do need semantic analysis (e.g., `DynamicValuesValidator`) accept the cost for higher-value diagnostics + +### ADR 4: SDK Discovery Chain + +**Context:** The extension needs to find the Connector SDK assemblies to pass to the LSP server. Different development scenarios require different discovery strategies. + +**Decision:** A 4-step fallback chain, tried in order: + +1. **Explicit setting** — `connectorSdk.sdkPath` in VS Code settings (`.dll` or `.nupkg`) +2. **Project assets** — Parse `obj/project.assets.json` to find SDK DLLs from the NuGet package cache (the standard `dotnet restore` workflow) +3. **Workspace `SDK/` folder** — Search for the newest `.nupkg` in a workspace-level `SDK/` directory +4. **Sibling repo build output** — Look for a `.nupkg` in `../../Connectors-NET-SDK/src/.../bin/Debug` (development scenario when the SDK repo is checked out alongside) + +**Rationale:** +- Step 1 provides an explicit override for non-standard setups +- Step 2 is zero-configuration for normal projects — if the user has run `dotnet restore`, the SDK DLLs are already available +- Step 3 supports manual SDK staging for evaluation or E2E testing +- Step 4 enables F5 debugging of the SDK and LSP extension side by side +- If no SDK is found and the project references the SDK package, the extension prompts the user to run `dotnet restore` diff --git a/README.md b/README.md index 68381ea..adbfade 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ The extension automatically: - `package.json` — Extension manifest with settings and commands - `SDK/` — SDK assembly storage directory - `Docs/` — Design documents and guides - - `architecture.md` — Architecture overview, analysis strategy, and design decisions *(planned — [#60](https://github.com/Azure/Connectors-NET-LSP/issues/60))* + - [`architecture.md`](Docs/architecture.md) — Architecture overview, data flow diagrams, analysis strategy, and design decisions - `design-attribute-context-completions.md` — Attribute-context completions design - `E2E_TESTING_GUIDE.md` — End-to-end testing guide - `sdk-lsp-server.sln` — Solution file From 0506087c2652c22ffdfb5eb81cadd65df1f3b62f Mon Sep 17 00:00:00 2001 From: David Burg Date: Tue, 12 May 2026 23:18:58 -0700 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20markdownlint=20=E2=80=94=20add=20cod?= =?UTF-8?q?e=20fence=20language=20and=20blank=20lines=20before=20lists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/architecture.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Docs/architecture.md b/Docs/architecture.md index c7b26f3..76b5cd0 100644 --- a/Docs/architecture.md +++ b/Docs/architecture.md @@ -53,7 +53,7 @@ The extension is the entry point. On activation it: #### Diagnostics Pipeline -``` +```text TextDocumentSyncHandler │ (on didOpen / didChange / didSave) ▼ @@ -235,6 +235,7 @@ For detailed validator authoring guidelines covering this strategy, see the [Arc **Decision:** Use Roslyn `MetadataReference.CreateFromFile()` to load SDK assemblies as metadata references for compilation, rather than `Assembly.Load()` or `Assembly.LoadFrom()`. **Rationale:** + - Avoids dependency loading issues — `Assembly.Load` pulls in transitive dependencies that may conflict with the server's own dependencies - No code execution risk — metadata references are read-only views of the assembly's type system - Compatible with Roslyn's `CSharpCompilation` — the same references are used for semantic analysis @@ -247,6 +248,7 @@ For detailed validator authoring guidelines covering this strategy, see the [Arc **Decision:** A single `CompilationService` instance caches one compilation per document URI. The cache key is `(textLength, contentChecksum, projectDirectory)` — a new compilation is created only when the document text changes or a different project context is detected. **Rationale:** + - Avoids redundant compilation creation across handlers processing the same document version - The `ConcurrentDictionary` cache retains only the latest version per URI to bound memory - When the caller passes a different `SyntaxTree` instance with identical text, the service replaces the tree in the cached compilation via `ReplaceSyntaxTree` (cheap operation) rather than rebuilding from scratch @@ -258,6 +260,7 @@ For detailed validator authoring guidelines covering this strategy, see the [Arc **Decision:** Diagnostic validators primarily use syntax-tree walking. Semantic model usage is reserved for specific checks that cannot be done syntactically (e.g., resolving `[DynamicValues]` attribute arguments to their declared operations). **Rationale:** + - Syntax parsing is ~1ms for typical files vs ~50-100ms for full compilation - Most SDK diagnostics (attribute presence, connector name validation, anti-pattern detection) can be checked syntactically by matching attribute names and string literal arguments against `SdkIndex` - Validators that do need semantic analysis (e.g., `DynamicValuesValidator`) accept the cost for higher-value diagnostics @@ -274,6 +277,7 @@ For detailed validator authoring guidelines covering this strategy, see the [Arc 4. **Sibling repo build output** — Look for a `.nupkg` in `../../Connectors-NET-SDK/src/.../bin/Debug` (development scenario when the SDK repo is checked out alongside) **Rationale:** + - Step 1 provides an explicit override for non-standard setups - Step 2 is zero-configuration for normal projects — if the user has run `dotnet restore`, the SDK DLLs are already available - Step 3 supports manual SDK staging for evaluation or E2E testing From f958d10a66849a01030f622f7132714921edc147 Mon Sep 17 00:00:00 2001 From: David Burg Date: Tue, 12 May 2026 23:29:35 -0700 Subject: [PATCH 3/7] Address PR feedback: fix relative links, --sdk-assembly docs, semantic validator mention --- Docs/architecture.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Docs/architecture.md b/Docs/architecture.md index 76b5cd0..9745e5c 100644 --- a/Docs/architecture.md +++ b/Docs/architecture.md @@ -14,7 +14,7 @@ The extension is the entry point. On activation it: 2. **Resolves the SDK path** — a 4-step fallback chain (see [SDK Discovery Chain](#adr-4-sdk-discovery-chain) below). 3. **Resolves the server DLL** — checks explicit setting → sibling debug build → bundled `server/` directory. 4. **Builds `initializationOptions`** — merges API config, connections (from `connections.json` and `local.settings.json`), CodeLens config, and telemetry config into a single JSON payload. -5. **Starts the LSP server** via `dotnet --sdk ` over stdio transport. +5. **Starts the LSP server** via `dotnet ` over stdio transport, passing `--sdk-assembly ...` when SDK resolution returns DLL paths (from explicit `.dll` setting or `project.assets.json`), or `--sdk ` when resolution returns a `.nupkg` file. 6. **Sets up file watchers** — watches `connections.json` and `local.settings.json` for changes; sends merged updates to the server via the `custom/updateConnections` notification. 7. **Starts a token refresh loop** — periodically acquires Azure tokens and pushes them to the server via `custom/updateApiConfig`. 8. **Registers commands** — `restartLanguageServer`, `openConnectionView`, `sdklsp.applyEdits` (for click-to-insert links in hover tooltips). @@ -78,7 +78,7 @@ textDocument/publishDiagnostics → VS Code | `DynamicValuesValidator` | `[DynamicValues]` parameter values against live API data | | `SdkAntiPatternValidator` | Common anti-patterns in SDK usage | -See [`.github/copilot-instructions.md`](.github/copilot-instructions.md#architecture-diagnostic-validators) for validator authoring guidelines, including the analysis strategy and key implementation details. +See [`.github/copilot-instructions.md`](../.github/copilot-instructions.md#architecture-diagnostic-validators) for validator authoring guidelines, including the analysis strategy and key implementation details. #### State Management (`Store/`) @@ -104,7 +104,7 @@ sequenceDiagram VSCode->>VSCode: Resolve SDK path (settings → assets → SDK/ → sibling repo) VSCode->>VSCode: Resolve server DLL path VSCode->>VSCode: Build initializationOptions (API config, connections, telemetry) - VSCode->>Server: Start process: dotnet SdkLspServer.dll --sdk + VSCode->>Server: Start process: dotnet SdkLspServer.dll --sdk-assembly ... or --sdk Server->>Index: SdkIndex.TryCreateAsync(nupkgPath) or TryCreateFromAssembliesAsync(dllPaths) Index-->>Server: SdkIndex (assemblies, types, connectorNames, triggerOperations) Server->>Server: Register DI services, handlers, validators @@ -221,10 +221,10 @@ sequenceDiagram The LSP server uses two levels of code analysis depending on the latency budget: -- **Syntax-only analysis** — Used by diagnostic validators and fast-path handlers. Parses the document with `CSharpSyntaxTree.ParseText()` and walks the syntax tree without building a full compilation. This avoids the cost of reference resolution and provides sub-100ms response times. -- **Semantic model analysis** — Used by Hover, Completion, and CodeLens handlers when they need type resolution, symbol lookup, or attribute inspection. Goes through `CompilationService.GetCompilation()` to get a full Roslyn `SemanticModel` with SDK and NuGet references. +- **Syntax-only analysis** — Used by most diagnostic validators and fast-path handlers. Parses the document with `CSharpSyntaxTree.ParseText()` and walks the syntax tree without building a full compilation. This avoids the cost of reference resolution and provides sub-100ms response times. +- **Semantic model analysis** — Used by Hover, Completion, and CodeLens handlers when they need type resolution, symbol lookup, or attribute inspection. Goes through `CompilationService.GetCompilation()` to get a full Roslyn `SemanticModel` with SDK and NuGet references. Some diagnostic validators also use semantic analysis for checks that cannot be done syntactically — notably `DynamicValuesValidator` (resolves `[DynamicValues]` attribute arguments to their declared operations) and `SdkAntiPatternValidator` (inspects method symbols and return types). -For detailed validator authoring guidelines covering this strategy, see the [Architecture: Diagnostic Validators](.github/copilot-instructions.md#architecture-diagnostic-validators) section in `.github/copilot-instructions.md`. +For detailed validator authoring guidelines covering this strategy, see the [Architecture: Diagnostic Validators](../.github/copilot-instructions.md#architecture-diagnostic-validators) section in `.github/copilot-instructions.md`. ## Key Design Decisions From 511b6a2b7341ae53951bd5227e6735d30350d9c4 Mon Sep 17 00:00:00 2001 From: David Burg Date: Tue, 12 May 2026 23:34:49 -0700 Subject: [PATCH 4/7] Address PR feedback round 2: lazy cache, dynamic invoke paths, anti-pattern semantic --- Docs/architecture.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Docs/architecture.md b/Docs/architecture.md index 9745e5c..fe67ca0 100644 --- a/Docs/architecture.md +++ b/Docs/architecture.md @@ -34,7 +34,7 @@ The extension is the entry point. On activation it: | Handler | Protocol Method | What It Does | |---------|----------------|--------------| -| `TextDocumentSyncHandler` | `didOpen`, `didChange`, `didSave`, `didClose` | Updates `BufferManager`, invalidates `CompilationService` cache, schedules debounced diagnostics via `DiagnosticPublisher`, requests CodeLens refresh when SDK usage is detected. | +| `TextDocumentSyncHandler` | `didOpen`, `didChange`, `didSave`, `didClose` | Updates `BufferManager`, schedules debounced diagnostics via `DiagnosticPublisher`, requests CodeLens refresh when SDK usage is detected. (`CompilationService` cache is replaced lazily when `GetCompilation` detects a changed text length/checksum — there is no explicit invalidation call.) | | `HoverHandler` | `textDocument/hover` | Parses the document, gets a `SemanticModel` from `CompilationService`, resolves the symbol at the cursor position, and returns rich Markdown hover content. For `[DynamicValues]` parameters, calls `DynamicOperationsRegistry` → `ApiService` to fetch live values from the connector API. | | `CompletionHandler` | `textDocument/completion` | Detects attribute argument contexts (connector names, operation IDs, trigger operations) and returns completion items from `SdkIndex`. For dynamic value parameters, fetches suggestions via `ApiService`. | | `CodeLensHandler` | `textDocument/codeLens` | Scans method signatures for SDK return types and attributes, and renders inline metadata (connector name, operation, HTTP method) above method declarations. Command names are configurable via `CodeLensConfig`. | @@ -208,7 +208,11 @@ sequenceDiagram Disc-->>Reg: Dictionary end Reg-->>HH: DynamicOperationMetadata (API path, HTTP method) - HH->>API: GET /api/connectors/{connector}/{operation} + alt DirectClient connection + HH->>API: Call runtime URL using discovered operation path/method + else Managed API connection + HH->>API: POST ARM /dynamicInvoke endpoint + end API->>Cache: Check cache (5min TTL, 1hr fail-safe) alt Cache miss API->>API: HTTP call with bearer token @@ -262,8 +266,8 @@ For detailed validator authoring guidelines covering this strategy, see the [Arc **Rationale:** - Syntax parsing is ~1ms for typical files vs ~50-100ms for full compilation -- Most SDK diagnostics (attribute presence, connector name validation, anti-pattern detection) can be checked syntactically by matching attribute names and string literal arguments against `SdkIndex` -- Validators that do need semantic analysis (e.g., `DynamicValuesValidator`) accept the cost for higher-value diagnostics +- Most SDK diagnostics (attribute presence, connector name validation) can be checked syntactically by matching attribute names and string literal arguments against `SdkIndex` +- Validators that need semantic analysis (e.g., `DynamicValuesValidator` for attribute argument resolution, `SdkAntiPatternValidator` for async/await and cancellation-token checks) accept the cost for higher-value diagnostics ### ADR 4: SDK Discovery Chain From 5f2f6d5cabf9f23ab30466624bc0ac535ebea220 Mon Sep 17 00:00:00 2001 From: David Burg Date: Tue, 12 May 2026 23:41:26 -0700 Subject: [PATCH 5/7] Address PR feedback round 3: initializationOptions scope, LSP lifecycle --- Docs/architecture.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Docs/architecture.md b/Docs/architecture.md index fe67ca0..e14497f 100644 --- a/Docs/architecture.md +++ b/Docs/architecture.md @@ -13,7 +13,7 @@ The extension is the entry point. On activation it: 1. **Scans for SDK references** — reads `.csproj` files to check for `PackageReference` to the Connector SDK. If no reference is found and no explicit `lspServerPath` is configured, the extension stays inactive. 2. **Resolves the SDK path** — a 4-step fallback chain (see [SDK Discovery Chain](#adr-4-sdk-discovery-chain) below). 3. **Resolves the server DLL** — checks explicit setting → sibling debug build → bundled `server/` directory. -4. **Builds `initializationOptions`** — merges API config, connections (from `connections.json` and `local.settings.json`), CodeLens config, and telemetry config into a single JSON payload. +4. **Builds `initializationOptions`** — merges API config (Azure subscription, resource group, bearer token) and connections (from `connections.json` and `local.settings.json`) into a JSON payload. (The server also accepts CodeLens and telemetry config in `initializationOptions`, but the VS Code extension does not currently send those.) 5. **Starts the LSP server** via `dotnet ` over stdio transport, passing `--sdk-assembly ...` when SDK resolution returns DLL paths (from explicit `.dll` setting or `project.assets.json`), or `--sdk ` when resolution returns a `.nupkg` file. 6. **Sets up file watchers** — watches `connections.json` and `local.settings.json` for changes; sends merged updates to the server via the `custom/updateConnections` notification. 7. **Starts a token refresh loop** — periodically acquires Azure tokens and pushes them to the server via `custom/updateApiConfig`. @@ -109,8 +109,9 @@ sequenceDiagram Index-->>Server: SdkIndex (assemblies, types, connectorNames, triggerOperations) Server->>Server: Register DI services, handlers, validators VSCode->>Server: initialize (with initializationOptions) - Server->>Server: Parse apiConfig, connections, codeLens, telemetry from initializationOptions - Server-->>VSCode: initialized (capabilities) + Server->>Server: Parse apiConfig, connections from initializationOptions + Server-->>VSCode: initialize response (capabilities) + VSCode->>Server: initialized notification VSCode->>VSCode: Setup file watchers (connections.json, local.settings.json) VSCode->>VSCode: Start token refresh loop ``` From a4ee20075d248927eb80d27b4d037fcd2c1a4f22 Mon Sep 17 00:00:00 2001 From: David Burg Date: Tue, 12 May 2026 23:47:43 -0700 Subject: [PATCH 6/7] Address PR feedback round 4: telemetry in diagram, restore prompt conditions --- Docs/architecture.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Docs/architecture.md b/Docs/architecture.md index e14497f..0b9fc9f 100644 --- a/Docs/architecture.md +++ b/Docs/architecture.md @@ -103,7 +103,7 @@ sequenceDiagram VSCode->>VSCode: Scan .csproj for SDK PackageReference VSCode->>VSCode: Resolve SDK path (settings → assets → SDK/ → sibling repo) VSCode->>VSCode: Resolve server DLL path - VSCode->>VSCode: Build initializationOptions (API config, connections, telemetry) + VSCode->>VSCode: Build initializationOptions (API config, connections) VSCode->>Server: Start process: dotnet SdkLspServer.dll --sdk-assembly ... or --sdk Server->>Index: SdkIndex.TryCreateAsync(nupkgPath) or TryCreateFromAssembliesAsync(dllPaths) Index-->>Server: SdkIndex (assemblies, types, connectorNames, triggerOperations) @@ -287,4 +287,4 @@ For detailed validator authoring guidelines covering this strategy, see the [Arc - Step 2 is zero-configuration for normal projects — if the user has run `dotnet restore`, the SDK DLLs are already available - Step 3 supports manual SDK staging for evaluation or E2E testing - Step 4 enables F5 debugging of the SDK and LSP extension side by side -- If no SDK is found and the project references the SDK package, the extension prompts the user to run `dotnet restore` +- If no SDK is found and the project both references the SDK package and is missing `obj/project.assets.json` (i.e., `dotnet restore` has not been run), the extension prompts the user to restore From 2fd6761a23d07e74ee1bf94f5d7d743811e0ad8c Mon Sep 17 00:00:00 2001 From: David Burg Date: Tue, 12 May 2026 23:53:58 -0700 Subject: [PATCH 7/7] Address PR feedback round 5: command prefixes, cached dynamic values, decompiler --- Docs/architecture.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Docs/architecture.md b/Docs/architecture.md index 0b9fc9f..e570483 100644 --- a/Docs/architecture.md +++ b/Docs/architecture.md @@ -17,7 +17,7 @@ The extension is the entry point. On activation it: 5. **Starts the LSP server** via `dotnet ` over stdio transport, passing `--sdk-assembly ...` when SDK resolution returns DLL paths (from explicit `.dll` setting or `project.assets.json`), or `--sdk ` when resolution returns a `.nupkg` file. 6. **Sets up file watchers** — watches `connections.json` and `local.settings.json` for changes; sends merged updates to the server via the `custom/updateConnections` notification. 7. **Starts a token refresh loop** — periodically acquires Azure tokens and pushes them to the server via `custom/updateApiConfig`. -8. **Registers commands** — `restartLanguageServer`, `openConnectionView`, `sdklsp.applyEdits` (for click-to-insert links in hover tooltips). +8. **Registers commands** — `connectorSdk.restartLanguageServer`, `connectorSdk.openConnectionView`, `sdklsp.applyEdits` (for click-to-insert links in hover tooltips). ### LSP Server @@ -75,7 +75,7 @@ textDocument/publishDiagnostics → VS Code | `AttributeValidator` | Attribute argument validity (e.g., connector names, operation IDs against `SdkIndex`) | | `ConnectionConfigValidator` | Whether required connections are configured for used connectors | | `TriggerPayloadValidator` | Trigger payload type conventions | -| `DynamicValuesValidator` | `[DynamicValues]` parameter values against live API data | +| `DynamicValuesValidator` | `[DynamicValues]` parameter values against cached dynamic values from `LSPStore.DynamicData` (populated by hover/completion handlers; no network calls) | | `SdkAntiPatternValidator` | Common anti-patterns in SDK usage | See [`.github/copilot-instructions.md`](../.github/copilot-instructions.md#architecture-diagnostic-validators) for validator authoring guidelines, including the analysis strategy and key implementation details. @@ -205,7 +205,7 @@ sequenceDiagram alt First call (not yet discovered) Reg->>Disc: DiscoverOperations(sdkIndex, compilationService) Disc->>CS: CreateSdkMetadataCompilation() - Disc->>Disc: Analyze SDK types via reflection metadata + Disc->>Disc: Decompile SDK method bodies via ICSharpCode.Decompiler to extract API paths Disc-->>Reg: Dictionary end Reg-->>HH: DynamicOperationMetadata (API path, HTTP method)