Compare commits

...

1 Commits

Author SHA1 Message Date
Sarah Fortune 7394c33d6a Update VSCode API linter rules
Add more restrictive rules:

* Prevent importing the entire vscode API `import * as vscode from "vscode"`
* Allowlist specific API calls that are not migrated yet.
* Prevent access to the fields in the vscode.ExtensionContext.
2025-09-15 15:40:12 -07:00
26 changed files with 173 additions and 193 deletions
+15 -2
View File
@@ -134,14 +134,27 @@
{
"includes": [
"**",
// This where the platform specific code lives
"!**/hosts/vscode/**",
"!src/extension.ts",
// Webview is not included
"!webview-ui/**",
// Test and dev files are not included
"!**/test/**",
"!**/*.test.ts",
"!src/dev/**",
"!src/extension.ts",
// These files are using VSCode API calls that still need to be migrated.
// These should eventually be migrated to use cross-platform HostBridge
// calls instead of using vscode directly.
"!src/integrations/git/commit-message-generator.ts",
"!src/core/controller/ui/openWalkthrough.ts",
"!src/integrations/terminal/**",
"!src/core/controller/ui/openWalkthrough.ts"
// These are still using the vscode LLM API.
"!src/core/api/providers/vscode-lm.ts",
"!src/core/api/transform/vscode-lm-format.ts",
"!src/core/controller/models/getVsCodeLmModels.ts",
// Part of the VSCode stubs.
"!src/standalone/vscode-context-utils.ts"
],
"plugins": [
"src/dev/grit/vscode-api.grit"
+6 -6
View File
@@ -1,4 +1,4 @@
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import {
migrateCustomInstructionsToGlobalRules,
migrateTaskHistoryToFile,
@@ -12,6 +12,7 @@ import "./utils/path" // necessary to have access to String.prototype.toPosix
import { HostProvider } from "@/hosts/host-provider"
import { FileContextTracker } from "./core/context/context-tracking/FileContextTracker"
import { ExtensionRegistryInfo } from "./registry"
import { ErrorService } from "./services/error"
import { featureFlagsService } from "./services/feature-flags"
import { initializeDistinctId } from "./services/logging/distinctId"
@@ -25,10 +26,9 @@ import { getLatestAnnouncementId } from "./utils/announcements"
* @param context
* @returns The webview provider
*/
export async function initialize(context: vscode.ExtensionContext): Promise<WebviewProvider> {
export async function initialize(context: ExtensionContext): Promise<WebviewProvider> {
// Set the distinct ID for logging and telemetry
await initializeDistinctId(context)
// Initialize PostHog client provider
PostHogClientProvider.getInstance()
@@ -60,9 +60,9 @@ export async function initialize(context: vscode.ExtensionContext): Promise<Webv
return sidebarWebview
}
async function showVersionUpdateAnnouncement(context: vscode.ExtensionContext) {
async function showVersionUpdateAnnouncement(context: ExtensionContext) {
// Version checking for autoupdate notification
const currentVersion = context.extension.packageJSON.version
const currentVersion = ExtensionRegistryInfo.version
const previousVersion = context.globalState.get<string>("clineVersion")
// Perform post-update actions if necessary
try {
@@ -71,7 +71,7 @@ async function showVersionUpdateAnnouncement(context: vscode.ExtensionContext) {
// Use the same condition as announcements: focus when there's a new announcement to show
const lastShownAnnouncementId = context.globalState.get<string>("lastShownAnnouncementId")
const latestAnnouncementId = getLatestAnnouncementId(context)
const latestAnnouncementId = getLatestAnnouncementId()
if (lastShownAnnouncementId !== latestAnnouncementId) {
// Focus Cline when there's a new announcement to show (major/minor updates or fresh installs)
@@ -2,7 +2,7 @@ import { getTaskMetadata, readTaskHistoryFromState, saveTaskMetadata } from "@co
import type { ClineMessage } from "@shared/ExtensionMessage"
import chokidar, { FSWatcher } from "chokidar"
import * as path from "path"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { Controller } from "@/core/controller"
import { getCwd } from "@/utils/path"
import type { FileMetadataEntry } from "./ContextTrackerTypes"
@@ -105,7 +105,7 @@ export class FileContextTracker {
* It also updates the metadata with the latest read/edit dates.
*/
async addFileToFileContextTracker(
context: vscode.ExtensionContext,
context: ExtensionContext,
taskId: string,
filePath: string,
source: FileMetadataEntry["record_source"],
@@ -282,7 +282,7 @@ export class FileContextTracker {
* Static method to clean up orphaned pending file context warnings at startup
* This removes warnings for tasks that may no longer exist
*/
static async cleanupOrphanedWarnings(context: vscode.ExtensionContext): Promise<void> {
static async cleanupOrphanedWarnings(context: ExtensionContext): Promise<void> {
const startTime = Date.now()
try {
const taskHistory = await readTaskHistoryFromState(context)
@@ -1,11 +1,11 @@
import { getTaskMetadata, saveTaskMetadata } from "@core/storage/disk"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
export class ModelContextTracker {
readonly taskId: string
private context: vscode.ExtensionContext
private context: ExtensionContext
constructor(context: vscode.ExtensionContext, taskId: string) {
constructor(context: ExtensionContext, taskId: string) {
this.context = context
this.taskId = taskId
}
+6 -5
View File
@@ -20,9 +20,10 @@ import axios from "axios"
import fs from "fs/promises"
import pWaitFor from "p-wait-for"
import * as path from "path"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { clineEnvConfig } from "@/config"
import { HostProvider } from "@/hosts/host-provider"
import { ExtensionRegistryInfo } from "@/registry"
import { AuthService } from "@/services/auth/AuthService"
import { getDistinctId } from "@/services/logging/distinctId"
import { telemetryService } from "@/services/telemetry"
@@ -55,7 +56,7 @@ export class Controller {
private workspaceManager?: WorkspaceRootManager
constructor(
readonly context: vscode.ExtensionContext,
readonly context: ExtensionContext,
id: string,
) {
this.id = id
@@ -108,7 +109,7 @@ export class Controller {
this.mcpHub = new McpHub(
() => ensureMcpServersDirectoryExists(),
() => ensureSettingsDirectoryExists(this.context),
this.context.extension?.packageJSON?.version ?? "1.0.0",
ExtensionRegistryInfo.version,
telemetryService,
)
@@ -683,11 +684,11 @@ export class Controller {
.sort((a, b) => b.ts - a.ts)
.slice(0, 100) // for now we're only getting the latest 100 tasks, but a better solution here is to only pass in 3 for recent task history, and then get the full task history on demand when going to the task history view (maybe with pagination?)
const latestAnnouncementId = getLatestAnnouncementId(this.context)
const latestAnnouncementId = getLatestAnnouncementId()
const shouldShowAnnouncement = lastShownAnnouncementId !== latestAnnouncementId
const platform = process.platform as Platform
const distinctId = getDistinctId()
const version = this.context.extension?.packageJSON?.version ?? ""
const version = ExtensionRegistryInfo.version
return {
version,
@@ -12,7 +12,7 @@ import type { Controller } from "../index"
*/
export async function onDidShowAnnouncement(controller: Controller, _request: EmptyRequest): Promise<Boolean> {
try {
const latestAnnouncementId = getLatestAnnouncementId(controller.context)
const latestAnnouncementId = getLatestAnnouncementId()
// Update the lastShownAnnouncementId to the current latestAnnouncementId
controller.stateManager.setGlobalState("lastShownAnnouncementId", latestAnnouncementId)
return Boolean.create({ value: false })
+2 -2
View File
@@ -9,7 +9,7 @@ import { getCommitInfo, getWorkingState } from "@utils/git"
import fs from "fs/promises"
import { isBinaryFile } from "isbinaryfile"
import * as path from "path"
import * as vscode from "vscode"
import { commands } from "vscode"
import { HostProvider } from "@/hosts/host-provider"
import { ShowMessageType } from "@/shared/proto/host/window"
import { DiagnosticSeverity } from "@/shared/proto/index.cline"
@@ -38,7 +38,7 @@ export async function openMention(mention?: string): Promise<void> {
} else if (mention === "problems") {
await HostProvider.workspace.openProblemsPanel({})
} else if (mention === "terminal") {
vscode.commands.executeCommand("workbench.action.terminal.focus")
commands.executeCommand("workbench.action.terminal.focus")
} else if (mention.startsWith("http")) {
await openExternal(mention)
}
+14 -14
View File
@@ -7,7 +7,7 @@ import { fileExistsAtPath } from "@utils/fs"
import fs from "fs/promises"
import os from "os"
import * as path from "path"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json",
@@ -62,7 +62,7 @@ export async function getDocumentsPath(): Promise<string> {
return path.join(os.homedir(), "Documents")
}
export async function ensureTaskDirectoryExists(context: vscode.ExtensionContext, taskId: string): Promise<string> {
export async function ensureTaskDirectoryExists(context: ExtensionContext, taskId: string): Promise<string> {
const globalStoragePath = context.globalStorageUri.fsPath
const taskDir = path.join(globalStoragePath, "tasks", taskId)
await fs.mkdir(taskDir, { recursive: true })
@@ -102,14 +102,14 @@ export async function ensureMcpServersDirectoryExists(): Promise<string> {
return mcpServersDir
}
export async function ensureSettingsDirectoryExists(context: vscode.ExtensionContext): Promise<string> {
export async function ensureSettingsDirectoryExists(context: ExtensionContext): Promise<string> {
const settingsDir = path.join(context.globalStorageUri.fsPath, "settings")
await fs.mkdir(settingsDir, { recursive: true })
return settingsDir
}
export async function getSavedApiConversationHistory(
context: vscode.ExtensionContext,
context: ExtensionContext,
taskId: string,
): Promise<Anthropic.MessageParam[]> {
const filePath = path.join(await ensureTaskDirectoryExists(context, taskId), GlobalFileNames.apiConversationHistory)
@@ -121,7 +121,7 @@ export async function getSavedApiConversationHistory(
}
export async function saveApiConversationHistory(
context: vscode.ExtensionContext,
context: ExtensionContext,
taskId: string,
apiConversationHistory: Anthropic.MessageParam[],
) {
@@ -134,7 +134,7 @@ export async function saveApiConversationHistory(
}
}
export async function getSavedClineMessages(context: vscode.ExtensionContext, taskId: string): Promise<ClineMessage[]> {
export async function getSavedClineMessages(context: ExtensionContext, taskId: string): Promise<ClineMessage[]> {
const filePath = path.join(await ensureTaskDirectoryExists(context, taskId), GlobalFileNames.uiMessages)
if (await fileExistsAtPath(filePath)) {
return JSON.parse(await fs.readFile(filePath, "utf8"))
@@ -150,7 +150,7 @@ export async function getSavedClineMessages(context: vscode.ExtensionContext, ta
return []
}
export async function saveClineMessages(context: vscode.ExtensionContext, taskId: string, uiMessages: ClineMessage[]) {
export async function saveClineMessages(context: ExtensionContext, taskId: string, uiMessages: ClineMessage[]) {
try {
const taskDir = await ensureTaskDirectoryExists(context, taskId)
const filePath = path.join(taskDir, GlobalFileNames.uiMessages)
@@ -160,7 +160,7 @@ export async function saveClineMessages(context: vscode.ExtensionContext, taskId
}
}
export async function getTaskMetadata(context: vscode.ExtensionContext, taskId: string): Promise<TaskMetadata> {
export async function getTaskMetadata(context: ExtensionContext, taskId: string): Promise<TaskMetadata> {
const filePath = path.join(await ensureTaskDirectoryExists(context, taskId), GlobalFileNames.taskMetadata)
try {
if (await fileExistsAtPath(filePath)) {
@@ -172,7 +172,7 @@ export async function getTaskMetadata(context: vscode.ExtensionContext, taskId:
return { files_in_context: [], model_usage: [] }
}
export async function saveTaskMetadata(context: vscode.ExtensionContext, taskId: string, metadata: TaskMetadata) {
export async function saveTaskMetadata(context: ExtensionContext, taskId: string, metadata: TaskMetadata) {
try {
const taskDir = await ensureTaskDirectoryExists(context, taskId)
const filePath = path.join(taskDir, GlobalFileNames.taskMetadata)
@@ -182,22 +182,22 @@ export async function saveTaskMetadata(context: vscode.ExtensionContext, taskId:
}
}
export async function ensureStateDirectoryExists(context: vscode.ExtensionContext): Promise<string> {
export async function ensureStateDirectoryExists(context: ExtensionContext): Promise<string> {
const stateDir = path.join(context.globalStorageUri.fsPath, "state")
await fs.mkdir(stateDir, { recursive: true })
return stateDir
}
export async function getTaskHistoryStateFilePath(context: vscode.ExtensionContext): Promise<string> {
export async function getTaskHistoryStateFilePath(context: ExtensionContext): Promise<string> {
return path.join(await ensureStateDirectoryExists(context), "taskHistory.json")
}
export async function taskHistoryStateFileExists(context: vscode.ExtensionContext): Promise<boolean> {
export async function taskHistoryStateFileExists(context: ExtensionContext): Promise<boolean> {
const filePath = await getTaskHistoryStateFilePath(context)
return fileExistsAtPath(filePath)
}
export async function readTaskHistoryFromState(context: vscode.ExtensionContext): Promise<HistoryItem[]> {
export async function readTaskHistoryFromState(context: ExtensionContext): Promise<HistoryItem[]> {
try {
const filePath = await getTaskHistoryStateFilePath(context)
if (await fileExistsAtPath(filePath)) {
@@ -216,7 +216,7 @@ export async function readTaskHistoryFromState(context: vscode.ExtensionContext)
}
}
export async function writeTaskHistoryToState(context: vscode.ExtensionContext, items: HistoryItem[]): Promise<void> {
export async function writeTaskHistoryToState(context: ExtensionContext, items: HistoryItem[]): Promise<void> {
try {
const filePath = await getTaskHistoryStateFilePath(context)
// Always create the file; if items is empty, write [] to ensure presence on first startup
+8 -8
View File
@@ -1,11 +1,11 @@
import fs from "fs/promises"
import path from "path"
import * as vscode from "vscode"
import { ExtensionContext, workspace } from "vscode"
import { HistoryItem } from "@/shared/HistoryItem"
import { ensureRulesDirectoryExists, readTaskHistoryFromState, writeTaskHistoryToState } from "./disk"
import { StateManager } from "./StateManager"
export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionContext) {
export async function migrateWorkspaceToGlobalStorage(context: ExtensionContext) {
// Keys to migrate from workspace storage back to global storage
const keysToMigrate = [
// Core settings
@@ -67,7 +67,7 @@ export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionC
}
}
export async function migrateTaskHistoryToFile(context: vscode.ExtensionContext) {
export async function migrateTaskHistoryToFile(context: ExtensionContext) {
try {
// Get data from old location
const vscodeGlobalStateTaskHistory = context.globalState.get<HistoryItem[] | undefined>("taskHistory")
@@ -122,7 +122,7 @@ export async function migrateTaskHistoryToFile(context: vscode.ExtensionContext)
}
export async function migrateMcpMarketplaceEnableSetting(mcpMarketplaceEnabledRaw: boolean | undefined): Promise<boolean> {
const config = vscode.workspace.getConfiguration("cline")
const config = workspace.getConfiguration("cline")
const mcpMarketplaceEnabled = config.get<boolean>("mcpMarketplace.enabled")
if (mcpMarketplaceEnabled !== undefined) {
// Remove from VSCode configuration
@@ -134,7 +134,7 @@ export async function migrateMcpMarketplaceEnableSetting(mcpMarketplaceEnabledRa
}
export async function migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw: boolean | undefined): Promise<boolean> {
const config = vscode.workspace.getConfiguration("cline")
const config = workspace.getConfiguration("cline")
const enableCheckpoints = config.get<boolean>("enableCheckpoints")
if (enableCheckpoints !== undefined) {
// Remove from VSCode configuration
@@ -144,7 +144,7 @@ export async function migrateEnableCheckpointsSetting(enableCheckpointsSettingRa
return enableCheckpointsSettingRaw ?? true
}
export async function migrateCustomInstructionsToGlobalRules(context: vscode.ExtensionContext) {
export async function migrateCustomInstructionsToGlobalRules(context: ExtensionContext) {
try {
const customInstructions = (await context.globalState.get("customInstructions")) as string | undefined
@@ -189,7 +189,7 @@ export async function migrateCustomInstructionsToGlobalRules(context: vscode.Ext
}
}
export async function migrateLegacyApiConfigurationToModeSpecific(context: vscode.ExtensionContext) {
export async function migrateLegacyApiConfigurationToModeSpecific(context: ExtensionContext) {
try {
// Check if migration is needed - if planModeApiProvider already exists, skip migration
const planModeApiProvider = await context.globalState.get("planModeApiProvider")
@@ -558,7 +558,7 @@ export async function migrateLegacyApiConfigurationToModeSpecific(context: vscod
}
}
export async function migrateWelcomeViewCompleted(context: vscode.ExtensionContext) {
export async function migrateWelcomeViewCompleted(context: ExtensionContext) {
try {
// Check if welcomeViewCompleted is already set
const welcomeViewCompleted = context.globalState.get("welcomeViewCompleted")
+2 -2
View File
@@ -12,7 +12,7 @@ import { FocusChainSettings } from "@shared/FocusChainSettings"
import { Mode } from "@shared/storage/types"
import { ClineDefaultTool } from "@shared/tools"
import { ClineAskResponse } from "@shared/WebviewMessage"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { modelDoesntSupportWebp } from "@/utils/model-utils"
import { ToolUse } from "../assistant-message"
import { ContextManager } from "../context/context-management/ContextManager"
@@ -66,7 +66,7 @@ export class ToolExecutor {
constructor(
// Core Services & Managers
private context: vscode.ExtensionContext,
private context: ExtensionContext,
private taskState: TaskState,
private messageStateHandler: MessageStateHandler,
private api: ApiHandler,
+2 -2
View File
@@ -1,7 +1,7 @@
import { isFocusChainItem } from "@shared/focus-chain-utils"
import * as fs from "fs/promises"
import * as path from "path"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { ensureTaskDirectoryExists } from "../../storage/disk"
/**
@@ -50,7 +50,7 @@ export function extractFocusChainListFromText(text: string): string | null {
* Returns the file path
*/
export async function ensureFocusChainFile(
context: vscode.ExtensionContext,
context: ExtensionContext,
taskId: string,
initialFocusChainContent?: string,
): Promise<string> {
+3 -3
View File
@@ -1,7 +1,7 @@
import { FocusChainSettings } from "@shared/FocusChainSettings"
import * as chokidar from "chokidar"
import * as fs from "fs/promises"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { telemetryService } from "@/services/telemetry"
import { ClineSay } from "../../../shared/ExtensionMessage"
import { Mode } from "../../../shared/storage/types"
@@ -21,7 +21,7 @@ export interface FocusChainDependencies {
taskId: string
taskState: TaskState
mode: Mode
context: vscode.ExtensionContext
context: ExtensionContext
stateManager: StateManager
postStateToWebview: () => Promise<void>
say: (type: ClineSay, text?: string, images?: string[], files?: string[], partial?: boolean) => Promise<number | undefined>
@@ -32,7 +32,7 @@ export class FocusChainManager {
private taskId: string
private taskState: TaskState
private mode: Mode
private context: vscode.ExtensionContext
private context: ExtensionContext
private stateManager: StateManager
private postStateToWebview: () => Promise<void>
private say: (
+3 -3
View File
@@ -67,7 +67,7 @@ import { execa } from "execa"
import pWaitFor from "p-wait-for"
import * as path from "path"
import { ulid } from "ulid"
import * as vscode from "vscode"
import { ExtensionContext, workspace } from "vscode"
import type { SystemPromptContext } from "@/core/prompts/system-prompt"
import { getSystemPrompt } from "@/core/prompts/system-prompt"
import { HostProvider } from "@/hosts/host-provider"
@@ -457,7 +457,7 @@ export class Task {
// While a task is ref'd by a controller, it will always have access to the extension context
// This error is thrown if the controller derefs the task after e.g., aborting the task
private getContext(): vscode.ExtensionContext {
private getContext(): ExtensionContext {
const context = this.controller.context
if (!context) {
throw new Error("Unable to access extension context")
@@ -1327,7 +1327,7 @@ export class Task {
* Migrates the disableBrowserTool setting from VSCode configuration to browserSettings
*/
private async migrateDisableBrowserToolSetting(): Promise<void> {
const config = vscode.workspace.getConfiguration("cline")
const config = workspace.getConfiguration("cline")
const disableBrowserTool = config.get<boolean>("disableBrowserTool")
if (disableBrowserTool !== undefined) {
+3 -3
View File
@@ -1,7 +1,7 @@
import Anthropic from "@anthropic-ai/sdk"
import CheckpointTracker from "@integrations/checkpoints/CheckpointTracker"
import getFolderSize from "get-folder-size"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { findLastIndex } from "@/shared/array"
import { combineApiRequests } from "@/shared/combineApiRequests"
import { combineCommandSequences } from "@/shared/combineCommandSequences"
@@ -13,7 +13,7 @@ import { ensureTaskDirectoryExists, saveApiConversationHistory, saveClineMessage
import { TaskState } from "./TaskState"
interface MessageStateHandlerParams {
context: vscode.ExtensionContext
context: ExtensionContext
taskId: string
ulid: string
taskIsFavorited?: boolean
@@ -29,7 +29,7 @@ export class MessageStateHandler {
private checkpointTracker: CheckpointTracker | undefined
private checkpointManagerErrorMessage: string | undefined
private updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>
private context: vscode.ExtensionContext
private context: ExtensionContext
private taskId: string
private ulid: string
private taskState: TaskState
@@ -5,6 +5,7 @@ import { showSystemNotification } from "@integrations/notifications"
import { createAndOpenGitHubIssue } from "@utils/github-url-utils"
import * as os from "os"
import { HostProvider } from "@/hosts/host-provider"
import { ExtensionRegistryInfo } from "@/registry"
import { ClineDefaultTool } from "@/shared/tools"
import type { ToolResponse } from "../../index"
import type { IPartialBlockHandler, IToolHandler } from "../ToolExecutorCoordinator"
@@ -74,7 +75,7 @@ export class ReportBugHandler implements IToolHandler, IPartialBlockHandler {
// Derive system information values algorithmically
const operatingSystem = os.platform() + " " + os.release()
const currentMode = config.mode
const clineVersion = config.context.extension.packageJSON.version
const clineVersion = ExtensionRegistryInfo.version
const host = await HostProvider.env.getHostVersion({})
const systemInfo = `${host.platform}: ${host.version}, Node.js: ${process.version}, Architecture: ${os.arch()}`
const apiConfig = config.services.stateManager.getApiConfiguration()
+2 -2
View File
@@ -12,7 +12,7 @@ import type { FocusChainSettings } from "@shared/FocusChainSettings"
import type { Mode } from "@shared/storage/types"
import type { ClineDefaultTool } from "@shared/tools"
import type { ClineAskResponse } from "@shared/WebviewMessage"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { WorkspaceRootManager } from "@/core/workspace"
import type { ContextManager } from "../../../context/context-management/ContextManager"
import type { StateManager } from "../../../storage/StateManager"
@@ -33,7 +33,7 @@ export interface TaskConfig {
mode: Mode
strictPlanModeEnabled: boolean
yoloModeToggled: boolean
context: vscode.ExtensionContext
context: ExtensionContext
// Multi-workspace support (optional for backward compatibility)
workspaceManager?: WorkspaceRootManager
+2 -3
View File
@@ -4,8 +4,7 @@ import { findLast } from "@shared/array"
import axios from "axios"
import { readFile } from "fs/promises"
import { v4 as uuidv4 } from "uuid"
import * as vscode from "vscode"
import { Uri } from "vscode"
import { ExtensionContext, Uri } from "vscode"
import { HostProvider } from "@/hosts/host-provider"
import { ShowMessageType } from "@/shared/proto/host/window"
import { WebviewProviderType } from "@/shared/webview/types"
@@ -20,7 +19,7 @@ export abstract class WebviewProvider {
private static lastActiveControllerId: string | null = null
constructor(
readonly context: vscode.ExtensionContext,
readonly context: ExtensionContext,
private readonly providerType: WebviewProviderType,
) {
WebviewProvider.activeInstances.add(this)
+70 -105
View File
@@ -1,117 +1,82 @@
or {
`$fn($args)` where {
or {
// File system operations
$fn <: `vscode.workspace.fs.stat`,
$fn <: `vscode.workspace.fs.writeFile`,
// Workspace operations
$fn <: `vscode.workspace.asRelativePath`,
$fn <: `vscode.workspace.getWorkspaceFolder`,
$fn <: `vscode.workspace.applyEdit`,
$fn <: `vscode.workspace.findFiles`,
$fn <: `vscode.workspace.openTextDocument`,
$fn <: `vscode.workspace.createFileSystemWatcher`,
$fn <: `vscode.workspace.onDidCreateFiles`,
$fn <: `vscode.workspace.onDidDeleteFiles`,
$fn <: `vscode.workspace.onDidRenameFiles`,
$fn <: `vscode.workspace.registerTextDocumentContentProvider`,
// Window operations
$fn <: `vscode.window.showTextDocument`,
$fn <: `vscode.window.onDidChangeActiveTextEditor`,
$fn <: `vscode.window.showErrorMessage`,
$fn <: `vscode.window.showInformationMessage`,
$fn <: `vscode.window.showWarningMessage`,
$fn <: `vscode.window.showInputBox`,
$fn <: `vscode.window.showOpenDialog`,
$fn <: `vscode.window.showSaveDialog`,
$fn <: `vscode.window.createTextEditorDecorationType`,
$fn <: `vscode.window.createOutputChannel`,
$fn <: `vscode.window.createWebviewPanel`,
$fn <: `vscode.window.registerWebviewViewProvider`,
$fn <: `vscode.window.registerUriHandler`,
//$fn <: `vscode.window.withProgress`,
// Environment operations
$fn <: `vscode.env.openExternal`,
$fn <: `vscode.env.clipboard.readText`,
$fn <: `vscode.env.clipboard.writeText`,
// Language operations
$fn <: `vscode.languages.onDidChangeDiagnostics`,
$fn <: `vscode.languages.registerCodeActionsProvider`,
$fn <: `vscode.languages.getDiagnostics`,
//$fn <: `vscode.lm.selectChatModels`,
// Debug operations
$fn <: `vscode.debug.onDidStartDebugSession`,
$fn <: `vscode.debug.onDidTerminateDebugSession`,
// Command operations
$fn <: `vscode.commands.registerCommand`,
// Note: executeCommand is handled separately with allowlist below
// Extension operations
$fn <: `vscode.extensions.getExtension`,
// Other operations
$fn <: `vscode.diff`,
//$fn <: `vscode.postMessage`,
$fn <: `vscode.Uri`,
// Text document operations
$fn <: `TextDocument.save`,
// Tab group operations
$fn <: `vscode.window.tabGroups.close`,
$fn <: `vscode.window.tabGroups.onDidChangeTabs`,
// Workspace text documents
$fn <: `vscode.workspace.textDocuments.find`
},
register_diagnostic(span=$fn, message="Replace this with methods from the host bridge provider or appropriate abstraction layer.")
// Block all vscode imports
`import * as vscode from "vscode"` where {
register_diagnostic(span=`import * as vscode from "vscode"`, message="VSCode API usage is only allowed in src/hosts/vscode/ or extension.ts. Using the VSCode API is not compatible cross-platform and won't work on JetBrains or CLI.")
},
// Block new vscode.commands.executeCommand usage (with allowlist for existing usage)
// This pattern matches executeCommand with any number of arguments using the spread operator
`vscode.commands.executeCommand($command, $...$args)` where {
`import vscode from "vscode"` where {
register_diagnostic(span=`import vscode from "vscode"`, message="VSCode API usage is only allowed in src/hosts/vscode/ or extension.ts. Using the VSCode API is not compatible cross-platform and won't work on JetBrains or CLI.")
},
`import { $imports } from "vscode"` where {
not or {
// Allowed existing command strings
$command <: `"workbench.action.terminal.focus"`
$imports <: `workspace`,
$imports <: `ExtensionContext`,
$imports <: `commands`,
$imports <: `LanguageModelChatSelector`
},
register_diagnostic(span=$command, message="New usage of vscode.commands.executeCommand is not allowed. Replace this with methods from the host bridge provider.")
register_diagnostic(span=$imports, message="VSCode API usage is only allowed in src/hosts/vscode/ or extension.ts. Using the VSCode API is not compatible cross-platform and won't work on JetBrains or CLI. Only 'workspace' and 'ExtensionContext' imports are allowed.")
},
// Also handle executeCommand calls with no additional arguments
`vscode.commands.executeCommand($command)` where {
not or {
// Allowed existing command strings (single argument version)
$command <: `"workbench.action.terminal.focus"`
},
register_diagnostic(span=$command, message="New usage of vscode.commands.executeCommand is not allowed. Replace this with methods from the host bridge provider.")
`const vscode = require("vscode")` where {
register_diagnostic(span=`const vscode = require("vscode")`, message="VSCode API usage is only allowed in src/hosts/vscode/ or extension.ts. Using the VSCode API is not compatible cross-platform and won't work on JetBrains or CLI.")
},
// Property access patterns (nested properties)
`vscode.$method.$var` where {
or {
$var <: `workspaceFolders`,
$var <: `appRoot`,
$var <: `machineId`,
$var <: `uriScheme`,
$var <: `isTelemetryEnabled`,
$var <: `onDidChangeTelemetryEnabled`,
$var <: `all`,
$var <: `activeTextEditor`,
$var <: `visibleTextEditors`,
$var <: `activeTabGroup`
},
register_diagnostic(span=$var, message="Use appropriate HostProvider methods or abstraction layer instead.")
`const { $imports } = require("vscode")` where {
register_diagnostic(span=`const { $imports } = require("vscode")`, message="VSCode API usage is only allowed in src/hosts/vscode/ or extension.ts. Using the VSCode API is not compatible cross-platform and won't work on JetBrains or CLI.")
},
// Direct vscode property access patterns
`vscode.$property` where {
or { $property <: `version` },
register_diagnostic(span=$property, message="Use appropriate HostProvider methods or abstraction layer instead.")
`import("vscode")` where {
register_diagnostic(span=`import("vscode")`, message="VSCode API usage is only allowed in src/hosts/vscode/ or extension.ts. Using the VSCode API is not compatible cross-platform and won't work on JetBrains or CLI.")
},
// Special case for vscode.window.tabGroups.all
`vscode.window.tabGroups.all` where {
register_diagnostic(message="Use appropriate HostProvider methods or abstraction layer instead.")
// Block workspace API calls except getConfiguration, when getConfiguration is migrated this exception can be removed.
`vscode.workspace.$method($args)` where {
not $method <: `getConfiguration`,
register_diagnostic(span=`vscode.workspace.$method`, message="Only vscode.workspace.getConfiguration() is allowed. Other workspace methods like $method are restricted for cross-platform compatibility.")
},
// Special case for vscode.extensions.all
`vscode.extensions.all` where {
register_diagnostic(message="Use appropriate HostProvider methods or abstraction layer instead.")
`workspace.$method($args)` where {
not $method <: `getConfiguration`,
register_diagnostic(span=`workspace.$method`, message="Only workspace.getConfiguration() is allowed. Other workspace methods like $method are restricted for cross-platform compatibility.")
},
// Block all vscode.env.* access (catches any property or method)
`vscode.env.$anything` where {
register_diagnostic(message="Use appropriate HostProvider methods or abstraction layer instead of vscode.env API.")
// Block vscode.commands API calls except executeCommand
`vscode.commands.$method($args)` where {
not $method <: `executeCommand`,
register_diagnostic(span=`vscode.commands.$method`, message="Only commands.executeCommand() is allowed. Other commands methods like $method are restricted for cross-platform compatibility.")
},
`commands.$method($args)` where {
not $method <: `executeCommand`,
register_diagnostic(span=`commands.$method`, message="Only commands.executeCommand() is allowed. Other commands methods like $method are restricted for cross-platform compatibility.")
},
// Block specific ExtensionContext properties and methods
`$context.asAbsolutePath($args)` where {
register_diagnostic(span=`$context.asAbsolutePath($args)`, message="ExtensionContext.asAbsolutePath() is restricted for cross-platform compatibility.")
},
`$context.storageUri` where {
register_diagnostic(span=`$context.storageUri`, message="ExtensionContext.storageUri is restricted for cross-platform compatibility.")
},
// `$context.globalStorageUri` where {
// register_diagnostic(span=`$context.globalStorageUri`, message="ExtensionContext.globalStorageUri is restricted for cross-platform compatibility.")
// },
`$context.logUri` where {
register_diagnostic(span=`$context.logUri`, message="ExtensionContext.logUri is restricted for cross-platform compatibility.")
},
// `$context.extensionUri` where {
// register_diagnostic(span=`$context.extensionUri`, message="ExtensionContext.extensionUri is restricted for cross-platform compatibility.")
// },
`$context.extensionPath` where {
register_diagnostic(span=`$context.extensionPath`, message="ExtensionContext.extensionPath is restricted for cross-platform compatibility.")
},
// `$context.workspaceState` where {
// register_diagnostic(span=`$context.workspaceState`, message="ExtensionContext.workspaceState is restricted for cross-platform compatibility.")
// },
// `$context.globalState` where {
// register_diagnostic(span=`$context.globalState`, message="ExtensionContext.globalState is restricted for cross-platform compatibility.")
// },
// `$context.secrets` where {
// register_diagnostic(span=`$context.secrets`, message="ExtensionContext.secrets is restricted for cross-platform compatibility.")
// },
`$context.subscriptions` where {
register_diagnostic(span=`$context.subscriptions`, message="ExtensionContext.subscriptions is restricted for cross-platform compatibility.")
},
`$context.environmentVariableCollection` where {
register_diagnostic(span=`$context.environmentVariableCollection`, message="ExtensionContext.environmentVariableCollection is restricted for cross-platform compatibility.")
},
`$context.extension` where {
register_diagnostic(span=`$context.extension`, message="ExtensionContext.extension is restricted for cross-platform compatibility.")
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { URI } from "vscode-uri"
import { WebviewProvider } from "@/core/webview"
import { WebviewProviderType } from "@/shared/webview/types"
@@ -7,7 +7,7 @@ export class ExternalWebviewProvider extends WebviewProvider {
// This hostname cannot be changed without updating the external webview handler.
private RESOURCE_HOSTNAME: string = "internal.resources"
constructor(context: vscode.ExtensionContext, providerType: WebviewProviderType) {
constructor(context: ExtensionContext, providerType: WebviewProviderType) {
super(context, providerType)
}
+3 -3
View File
@@ -12,7 +12,7 @@ import { getApiMetrics } from "@shared/getApiMetrics"
import { HistoryItem } from "@shared/HistoryItem"
import { ClineCheckpointRestore } from "@shared/WebviewMessage"
import pTimeout from "p-timeout"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
import { HostProvider } from "@/hosts/host-provider"
import { ShowMessageType } from "@/shared/proto/host/window"
import { MessageStateHandler } from "../../core/task/message-state"
@@ -39,7 +39,7 @@ interface CheckpointManagerServices {
readonly fileContextTracker: FileContextTracker
readonly diffViewProvider: DiffViewProvider
readonly messageStateHandler: MessageStateHandler
readonly context: vscode.ExtensionContext
readonly context: ExtensionContext
readonly taskState: TaskState
}
interface CheckpointManagerCallbacks {
@@ -872,7 +872,7 @@ export class TaskCheckpointManager implements ICheckpointManager {
/**
* Gets the extension context with proper error handling
*/
private getContext(): vscode.ExtensionContext {
private getContext(): ExtensionContext {
if (!this.services.context) {
throw new Error("Unable to access extension context")
}
+5 -5
View File
@@ -14,7 +14,7 @@ import * as path from "path"
import PCR from "puppeteer-chromium-resolver"
import type { ConsoleMessage, ScreenshotOptions } from "puppeteer-core"
import { Browser, connect, launch, Page, TimeoutError } from "puppeteer-core"
import * as vscode from "vscode"
import { ExtensionContext, workspace } from "vscode"
import { telemetryService } from "@/services/telemetry"
import { discoverChromeInstances, isPortOpen, testBrowserConnection } from "./BrowserDiscovery"
@@ -42,7 +42,7 @@ function splitArgs(str?: string | null): string[] {
}
export class BrowserSession {
private context: vscode.ExtensionContext
private context: ExtensionContext
private browser?: Browser
private page?: Page
private currentMousePosition?: string
@@ -57,7 +57,7 @@ export class BrowserSession {
private browserActions: string[] = []
private ulid?: string
constructor(context: vscode.ExtensionContext, browserSettings: BrowserSettings, useWebp: boolean = true) {
constructor(context: ExtensionContext, browserSettings: BrowserSettings, useWebp: boolean = true) {
this.context = context
this.browserSettings = browserSettings
this.useWebp = useWebp
@@ -83,8 +83,8 @@ export class BrowserSession {
* Migrates the chromeExecutablePath setting from VSCode configuration to browserSettings
*/
private async migrateChromeExecutablePathSetting(): Promise<void> {
const config = vscode.workspace.getConfiguration("cline")
const configPath = vscode.workspace.getConfiguration("cline").get<string>("chromeExecutablePath")
const config = workspace.getConfiguration("cline")
const configPath = workspace.getConfiguration("cline").get<string>("chromeExecutablePath")
if (configPath !== undefined) {
this.browserSettings.chromeExecutablePath = configPath
+3 -3
View File
@@ -7,7 +7,7 @@ import * as path from "path"
import PCR from "puppeteer-chromium-resolver"
import { Browser, launch, Page } from "puppeteer-core"
import TurndownService from "turndown"
import * as vscode from "vscode"
import { ExtensionContext } from "vscode"
interface PCRStats {
puppeteer: { launch: typeof launch }
@@ -15,11 +15,11 @@ interface PCRStats {
}
export class UrlContentFetcher {
private context: vscode.ExtensionContext
private context: ExtensionContext
private browser?: Browser
private page?: Page
constructor(context: vscode.ExtensionContext) {
constructor(context: ExtensionContext) {
this.context = context
}
@@ -1,5 +1,5 @@
import { PostHog } from "posthog-node"
import * as vscode from "vscode"
import { workspace } from "vscode"
import { HostProvider } from "@/hosts/host-provider"
import { getDistinctId } from "@/services/logging/distinctId"
import { PostHogClientProvider } from "@/services/posthog/PostHogClientProvider"
@@ -54,7 +54,7 @@ export class PostHogErrorProvider implements IErrorProvider {
}
// Check extension-specific telemetry setting
const config = vscode.workspace.getConfiguration("cline")
const config = workspace.getConfiguration("cline")
if (config.get("telemetrySetting") === "disabled") {
this.errorSettings.enabled = false
}
@@ -139,7 +139,7 @@ export class PostHogErrorProvider implements IErrorProvider {
if (hostSettings.isEnabled === Setting.DISABLED) {
return "off"
}
const config = vscode.workspace.getConfiguration("telemetry")
const config = workspace.getConfiguration("telemetry")
return config?.get<ErrorSettings["level"]>("telemetryLevel") || "all"
}
@@ -1,5 +1,5 @@
import { PostHog } from "posthog-node"
import * as vscode from "vscode"
import { workspace } from "vscode"
import { HostProvider } from "@/hosts/host-provider"
import { getDistinctId, setDistinctId } from "@/services/logging/distinctId"
import { Setting } from "@/shared/proto/index.host"
@@ -136,7 +136,7 @@ export class PostHogTelemetryProvider implements ITelemetryProvider {
if (hostSettings.isEnabled === Setting.DISABLED) {
return "off"
}
const config = vscode.workspace.getConfiguration("telemetry")
const config = workspace.getConfiguration("telemetry")
return config?.get<TelemetrySettings["level"]>("telemetryLevel") || "all"
}
}
+4 -3
View File
@@ -1,4 +1,4 @@
import * as vscode from "vscode"
import { ExtensionRegistryInfo } from "@/registry"
/**
* Gets the latest announcement ID based on the extension version
@@ -7,6 +7,7 @@ import * as vscode from "vscode"
* @param context The VSCode extension context
* @returns The announcement ID string (major.minor version) or empty string if unavailable
*/
export function getLatestAnnouncementId(context: vscode.ExtensionContext): string {
return context.extension?.packageJSON?.version?.split(".").slice(0, 2).join(".") ?? ""
export function getLatestAnnouncementId(): string {
const version = ExtensionRegistryInfo.version
return version?.split(".").slice(0, 2).join(".") ?? ""
}
+4 -4
View File
@@ -1,5 +1,5 @@
import { userInfo } from "os"
import * as vscode from "vscode"
import { workspace } from "vscode"
const SHELL_PATHS = {
// Windows paths
@@ -46,7 +46,7 @@ type LinuxTerminalProfiles = Record<string, LinuxTerminalProfile>
function getWindowsTerminalConfig() {
try {
const config = vscode.workspace.getConfiguration("terminal.integrated")
const config = workspace.getConfiguration("terminal.integrated")
const defaultProfileName = config.get<string>("defaultProfile.windows")
const profiles = config.get<WindowsTerminalProfiles>("profiles.windows") || {}
return { defaultProfileName, profiles }
@@ -57,7 +57,7 @@ function getWindowsTerminalConfig() {
function getMacTerminalConfig() {
try {
const config = vscode.workspace.getConfiguration("terminal.integrated")
const config = workspace.getConfiguration("terminal.integrated")
const defaultProfileName = config.get<string>("defaultProfile.osx")
const profiles = config.get<MacTerminalProfiles>("profiles.osx") || {}
return { defaultProfileName, profiles }
@@ -68,7 +68,7 @@ function getMacTerminalConfig() {
function getLinuxTerminalConfig() {
try {
const config = vscode.workspace.getConfiguration("terminal.integrated")
const config = workspace.getConfiguration("terminal.integrated")
const defaultProfileName = config.get<string>("defaultProfile.linux")
const profiles = config.get<LinuxTerminalProfiles>("profiles.linux") || {}
return { defaultProfileName, profiles }