From dca865c354f7992e2d18f41faf7d8aded196811a Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Fri, 3 Jan 2025 22:24:33 +0100 Subject: [PATCH 1/9] Restructure extension. --- src/extension.ts | 276 +---------------- src/providers/taskProvider.ts | 280 ++++++++++++++++++ src/test/{suite => providers}/index.ts | 0 .../taskProvider.test.ts} | 8 +- 4 files changed, 286 insertions(+), 278 deletions(-) create mode 100644 src/providers/taskProvider.ts rename src/test/{suite => providers}/index.ts (100%) rename src/test/{suite/extension.test.ts => providers/taskProvider.test.ts} (96%) diff --git a/src/extension.ts b/src/extension.ts index 89aa0e0..5006174 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,280 +1,8 @@ import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; - -type TreeItemType = FolderItem | ModuleItem | TaskItem; - -class FolderItem extends vscode.TreeItem { - constructor( - public readonly label: string, - public readonly children: (FolderItem | ModuleItem)[] = [], - public readonly folderPath: string - ) { - super(label, vscode.TreeItemCollapsibleState.Expanded); - this.contextValue = 'folder'; - this.iconPath = new vscode.ThemeIcon('folder'); - this.tooltip = folderPath; - } -} - -class ModuleItem extends vscode.TreeItem { - constructor( - public readonly label: string, - public readonly children: TaskItem[] = [], - public readonly filePath: string - ) { - super(label, vscode.TreeItemCollapsibleState.Collapsed); - this.contextValue = 'module'; - this.iconPath = new vscode.ThemeIcon('symbol-file'); - this.tooltip = filePath; - this.command = { - command: 'vscode.open', - title: 'Open Module File', - arguments: [vscode.Uri.file(filePath)], - }; - } -} - -class TaskItem extends vscode.TreeItem { - constructor( - public readonly label: string, - public readonly filePath: string, - public readonly lineNumber: number - ) { - super(label, vscode.TreeItemCollapsibleState.None); - this.contextValue = 'task'; - this.iconPath = new vscode.ThemeIcon('symbol-method'); - this.tooltip = `${this.label} - ${path.basename(this.filePath)}:${this.lineNumber}`; - this.description = path.basename(this.filePath); - this.command = { - command: 'vscode.open', - title: 'Open Task File', - arguments: [ - vscode.Uri.file(this.filePath), - { - selection: new vscode.Range( - new vscode.Position(this.lineNumber - 1, 0), - new vscode.Position(this.lineNumber - 1, 0) - ), - }, - ], - }; - } -} - -export class PyTaskProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = - new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = - this._onDidChangeTreeData.event; - private fileSystemWatcher: vscode.FileSystemWatcher; - - constructor() { - this.fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/task_*.py'); - - this.fileSystemWatcher.onDidCreate(() => { - this.refresh(); - }); - - this.fileSystemWatcher.onDidChange(() => { - this.refresh(); - }); - - this.fileSystemWatcher.onDidDelete(() => { - this.refresh(); - }); - } - - dispose() { - this.fileSystemWatcher.dispose(); - } - - refresh(): void { - this._onDidChangeTreeData.fire(); - } - - getTreeItem(element: TreeItemType): vscode.TreeItem { - return element; - } - - async getChildren(element?: TreeItemType): Promise { - if (!element) { - return this.buildFileTree(); - } - - if (element instanceof FolderItem) { - return element.children; - } - - if (element instanceof ModuleItem) { - return element.children; - } - - return []; - } - - private async buildFileTree(): Promise { - const workspaceFolders = vscode.workspace.workspaceFolders; - if (!workspaceFolders) { - return []; - } - - const rootItems = new Map(); - - // Get all task modules across the workspace - const taskFiles = await vscode.workspace.findFiles( - '**/task_*.py', - '{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}' - ); - - // Process each task module - for (const taskFile of taskFiles) { - const relativePath = path.relative(workspaceFolders[0].uri.fsPath, taskFile.fsPath); - const dirPath = path.dirname(relativePath); - const fileName = path.basename(taskFile.fsPath); - - // Create folder hierarchy - let currentPath = ''; - let currentItems = rootItems; - const pathParts = dirPath.split(path.sep); - - // Skip if it's in the root - if (dirPath !== '.') { - for (const part of pathParts) { - currentPath = currentPath ? path.join(currentPath, part) : part; - const fullPath = path.join(workspaceFolders[0].uri.fsPath, currentPath); - - if (!currentItems.has(currentPath)) { - const newFolder = new FolderItem(part, [], fullPath); - currentItems.set(currentPath, newFolder); - } - - const folderItem = currentItems.get(currentPath); - if (folderItem instanceof FolderItem) { - currentItems = new Map( - folderItem.children - .filter((child) => child instanceof FolderItem) - .map((child) => [path.basename(child.label), child as FolderItem]) - ); - } - } - } - - // Create module and its tasks - const content = fs.readFileSync(taskFile.fsPath, 'utf8'); - const taskItems = this.findTaskFunctions(taskFile.fsPath, content); - const moduleItem = new ModuleItem(fileName, taskItems, taskFile.fsPath); - - // Add module to appropriate folder or root - if (dirPath === '.') { - rootItems.set(fileName, moduleItem); - } else { - const parentFolder = rootItems.get(dirPath); - if (parentFolder instanceof FolderItem) { - parentFolder.children.push(moduleItem); - } - } - } - - // Sort everything - const result = Array.from(rootItems.values()); - - // Sort folders and modules - result.sort((a, b) => { - // Folders come before modules - if (a instanceof FolderItem && !(b instanceof FolderItem)) return -1; - if (!(a instanceof FolderItem) && b instanceof FolderItem) return 1; - // Alphabetical sort within same type - return a.label.localeCompare(b.label); - }); - - return result; - } - - findTaskFunctions(filePath: string, content: string): TaskItem[] { - // Find out whether the task decorator is used in the file. - - // Booleans to track if the task decorator is imported as `from pytask import task` - // and used as `@task` or `import pytask` and used as `@pytask.task`. - let hasTaskImport = false; - let taskAlias = 'task'; // default name for 'from pytask import task' - let pytaskAlias = 'pytask'; // default name for 'import pytask' - let hasPytaskImport = false; - - // Match the import statements - // Handle various import patterns: - // - from pytask import task - // - from pytask import task as t - // - from pytask import Product, task - // - from pytask import (Product, task) - const fromPytaskImport = content.match( - /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/ - ); - const importPytask = content.match(/import\s+pytask(?:\s+as\s+(\w+))?\s*$/m); - - if (fromPytaskImport) { - hasTaskImport = true; - if (fromPytaskImport[1]) { - taskAlias = fromPytaskImport[1]; - } - } - - if (importPytask) { - hasPytaskImport = true; - // If there's an alias (import pytask as something), use it - pytaskAlias = importPytask[1] || 'pytask'; - } - - // Find the tasks. - const tasks: TaskItem[] = []; - const lines = content.split('\n'); - - let isDecorated = false; - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - - // Check for decorators - if (line.startsWith('@')) { - // Handle both @task and @pytask.task(...) patterns - isDecorated = - (hasTaskImport && line === `@${taskAlias}`) || - (hasPytaskImport && line.startsWith(`@${pytaskAlias}.task`)); - continue; - } - - // Check for function definitions - const funcMatch = line.match(/^def\s+(\w+)\s*\(/); - if (funcMatch) { - const funcName = funcMatch[1]; - // Add if it's a task_* function or has a task decorator - if (funcName.startsWith('task_') || isDecorated) { - tasks.push(new TaskItem(funcName, filePath, i + 1)); - } - isDecorated = false; // Reset decorator flag - } - } - - // Sort the tasks by name. - tasks.sort((a, b) => a.label.localeCompare(b.label)); - - return tasks; - } -} +import { activate as activateTaskProvider } from './providers/taskProvider'; export function activate(context: vscode.ExtensionContext) { - const pytaskProvider = new PyTaskProvider(); - const treeView = vscode.window.createTreeView('pytaskExplorer', { - treeDataProvider: pytaskProvider, - showCollapseAll: true, - }); - - context.subscriptions.push(treeView); - - const refreshCommand = vscode.commands.registerCommand('pytask.refresh', () => { - pytaskProvider.refresh(); - }); - - context.subscriptions.push(refreshCommand, pytaskProvider); + activateTaskProvider(context); } export function deactivate() {} diff --git a/src/providers/taskProvider.ts b/src/providers/taskProvider.ts new file mode 100644 index 0000000..997856d --- /dev/null +++ b/src/providers/taskProvider.ts @@ -0,0 +1,280 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; + +type TreeItemType = FolderItem | ModuleItem | TaskItem; + +class FolderItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly children: (FolderItem | ModuleItem)[] = [], + public readonly folderPath: string + ) { + super(label, vscode.TreeItemCollapsibleState.Expanded); + this.contextValue = 'folder'; + this.iconPath = new vscode.ThemeIcon('folder'); + this.tooltip = folderPath; + } +} + +class ModuleItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly children: TaskItem[] = [], + public readonly filePath: string + ) { + super(label, vscode.TreeItemCollapsibleState.Collapsed); + this.contextValue = 'module'; + this.iconPath = new vscode.ThemeIcon('symbol-file'); + this.tooltip = filePath; + this.command = { + command: 'vscode.open', + title: 'Open Module File', + arguments: [vscode.Uri.file(filePath)], + }; + } +} + +class TaskItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly filePath: string, + public readonly lineNumber: number + ) { + super(label, vscode.TreeItemCollapsibleState.None); + this.contextValue = 'task'; + this.iconPath = new vscode.ThemeIcon('symbol-method'); + this.tooltip = `${this.label} - ${path.basename(this.filePath)}:${this.lineNumber}`; + this.description = path.basename(this.filePath); + this.command = { + command: 'vscode.open', + title: 'Open Task File', + arguments: [ + vscode.Uri.file(this.filePath), + { + selection: new vscode.Range( + new vscode.Position(this.lineNumber - 1, 0), + new vscode.Position(this.lineNumber - 1, 0) + ), + }, + ], + }; + } +} + +export class PyTaskProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = + new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = + this._onDidChangeTreeData.event; + private fileSystemWatcher: vscode.FileSystemWatcher; + + constructor() { + this.fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/task_*.py'); + + this.fileSystemWatcher.onDidCreate(() => { + this.refresh(); + }); + + this.fileSystemWatcher.onDidChange(() => { + this.refresh(); + }); + + this.fileSystemWatcher.onDidDelete(() => { + this.refresh(); + }); + } + + dispose() { + this.fileSystemWatcher.dispose(); + } + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: TreeItemType): vscode.TreeItem { + return element; + } + + async getChildren(element?: TreeItemType): Promise { + if (!element) { + return this.buildFileTree(); + } + + if (element instanceof FolderItem) { + return element.children; + } + + if (element instanceof ModuleItem) { + return element.children; + } + + return []; + } + + private async buildFileTree(): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + return []; + } + + const rootItems = new Map(); + + // Get all task modules across the workspace + const taskFiles = await vscode.workspace.findFiles( + '**/task_*.py', + '{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}' + ); + + // Process each task module + for (const taskFile of taskFiles) { + const relativePath = path.relative(workspaceFolders[0].uri.fsPath, taskFile.fsPath); + const dirPath = path.dirname(relativePath); + const fileName = path.basename(taskFile.fsPath); + + // Create folder hierarchy + let currentPath = ''; + let currentItems = rootItems; + const pathParts = dirPath.split(path.sep); + + // Skip if it's in the root + if (dirPath !== '.') { + for (const part of pathParts) { + currentPath = currentPath ? path.join(currentPath, part) : part; + const fullPath = path.join(workspaceFolders[0].uri.fsPath, currentPath); + + if (!currentItems.has(currentPath)) { + const newFolder = new FolderItem(part, [], fullPath); + currentItems.set(currentPath, newFolder); + } + + const folderItem = currentItems.get(currentPath); + if (folderItem instanceof FolderItem) { + currentItems = new Map( + folderItem.children + .filter((child) => child instanceof FolderItem) + .map((child) => [path.basename(child.label), child as FolderItem]) + ); + } + } + } + + // Create module and its tasks + const content = fs.readFileSync(taskFile.fsPath, 'utf8'); + const taskItems = this.findTaskFunctions(taskFile.fsPath, content); + const moduleItem = new ModuleItem(fileName, taskItems, taskFile.fsPath); + + // Add module to appropriate folder or root + if (dirPath === '.') { + rootItems.set(fileName, moduleItem); + } else { + const parentFolder = rootItems.get(dirPath); + if (parentFolder instanceof FolderItem) { + parentFolder.children.push(moduleItem); + } + } + } + + // Sort everything + const result = Array.from(rootItems.values()); + + // Sort folders and modules + result.sort((a, b) => { + // Folders come before modules + if (a instanceof FolderItem && !(b instanceof FolderItem)) return -1; + if (!(a instanceof FolderItem) && b instanceof FolderItem) return 1; + // Alphabetical sort within same type + return a.label.localeCompare(b.label); + }); + + return result; + } + + findTaskFunctions(filePath: string, content: string): TaskItem[] { + // Find out whether the task decorator is used in the file. + + // Booleans to track if the task decorator is imported as `from pytask import task` + // and used as `@task` or `import pytask` and used as `@pytask.task`. + let hasTaskImport = false; + let taskAlias = 'task'; // default name for 'from pytask import task' + let pytaskAlias = 'pytask'; // default name for 'import pytask' + let hasPytaskImport = false; + + // Match the import statements + // Handle various import patterns: + // - from pytask import task + // - from pytask import task as t + // - from pytask import Product, task + // - from pytask import (Product, task) + const fromPytaskImport = content.match( + /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/ + ); + const importPytask = content.match(/import\s+pytask(?:\s+as\s+(\w+))?\s*$/m); + + if (fromPytaskImport) { + hasTaskImport = true; + if (fromPytaskImport[1]) { + taskAlias = fromPytaskImport[1]; + } + } + + if (importPytask) { + hasPytaskImport = true; + // If there's an alias (import pytask as something), use it + pytaskAlias = importPytask[1] || 'pytask'; + } + + // Find the tasks. + const tasks: TaskItem[] = []; + const lines = content.split('\n'); + + let isDecorated = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + // Check for decorators + if (line.startsWith('@')) { + // Handle both @task and @pytask.task(...) patterns + isDecorated = + (hasTaskImport && line === `@${taskAlias}`) || + (hasPytaskImport && line.startsWith(`@${pytaskAlias}.task`)); + continue; + } + + // Check for function definitions + const funcMatch = line.match(/^def\s+(\w+)\s*\(/); + if (funcMatch) { + const funcName = funcMatch[1]; + // Add if it's a task_* function or has a task decorator + if (funcName.startsWith('task_') || isDecorated) { + tasks.push(new TaskItem(funcName, filePath, i + 1)); + } + isDecorated = false; // Reset decorator flag + } + } + + // Sort the tasks by name. + tasks.sort((a, b) => a.label.localeCompare(b.label)); + + return tasks; + } +} + +export function activate(context: vscode.ExtensionContext): vscode.TreeView { + const pytaskProvider = new PyTaskProvider(); + const treeView = vscode.window.createTreeView('pytaskExplorer', { + treeDataProvider: pytaskProvider, + showCollapseAll: true, + }); + + context.subscriptions.push(treeView); + + const refreshCommand = vscode.commands.registerCommand('pytask.refresh', () => { + pytaskProvider.refresh(); + }); + + context.subscriptions.push(refreshCommand, pytaskProvider); + + return treeView; +} diff --git a/src/test/suite/index.ts b/src/test/providers/index.ts similarity index 100% rename from src/test/suite/index.ts rename to src/test/providers/index.ts diff --git a/src/test/suite/extension.test.ts b/src/test/providers/taskProvider.test.ts similarity index 96% rename from src/test/suite/extension.test.ts rename to src/test/providers/taskProvider.test.ts index 04f6e92..93e44fa 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/providers/taskProvider.test.ts @@ -3,7 +3,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import { expect } from 'chai'; -import { PyTaskProvider } from '../../extension'; +import { PyTaskProvider } from '../../providers/taskProvider'; suite('PyTask Extension Test Suite', function () { // Increase timeout for all tests @@ -72,7 +72,7 @@ def not_a_task(): expect(tasks.length).to.equal(2, 'Should find exactly 2 tasks in the module'); // Verify task names - const taskNames = tasks.map((item) => item.label); + const taskNames = tasks.map((item: vscode.TreeItem) => item.label); expect(taskNames).to.include('task_one', 'Should find task_one'); expect(taskNames).to.include('task_two', 'Should find task_two'); expect(taskNames).to.not.include('not_a_task', 'Should not find not_a_task'); @@ -105,7 +105,7 @@ def not_a_task(): expect(items.length).to.equal(2, 'Should find both task modules'); // Find the empty module - const emptyModule = items.find((item) => item.label === 'task_empty.py'); + const emptyModule = items.find((item: vscode.TreeItem) => item.label === 'task_empty.py'); expect(emptyModule).to.exist; expect(emptyModule!.contextValue).to.equal( 'module', @@ -154,7 +154,7 @@ def not_a_task(): expect(tasks.length).to.equal(3, 'Should find exactly 3 tasks in the module'); // Verify task names - const taskNames = tasks.map((item) => item.label); + const taskNames = tasks.map((item: vscode.TreeItem) => item.label); expect(taskNames).to.include('task_one', 'Should find task_one'); expect(taskNames).to.include('task_two', 'Should find task_two'); expect(taskNames).to.include('task_three', 'Should find the newly added task_three'); From 3b6806fa30707e6a8387814bd766e6f222bcd13d Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Fri, 3 Jan 2025 22:32:11 +0100 Subject: [PATCH 2/9] Fix. --- src/test/runTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/runTest.ts b/src/test/runTest.ts index 5640b49..333c99e 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -17,7 +17,7 @@ async function main() { const extensionDevelopmentPath = path.resolve(__dirname, '../../'); // The path to the extension test script - const extensionTestsPath = path.resolve(__dirname, './suite/index'); + const extensionTestsPath = path.resolve(__dirname, './providers/index'); // Download VS Code, unzip it and run the integration test await runTests({ From 00df385ee386750fce7d1238f7bdfa5cef375dec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 21:32:38 +0000 Subject: [PATCH 3/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- eslint.config.mjs | 40 ++--- src/extension.ts | 4 +- src/providers/taskProvider.ts | 107 +++++++------ src/test/providers/index.ts | 14 +- src/test/providers/taskProvider.test.ts | 199 ++++++++++++++---------- src/test/runTest.ts | 20 +-- 6 files changed, 221 insertions(+), 163 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 1b93d55..3966acc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,45 +1,45 @@ -import js from '@eslint/js'; -import typescript from '@typescript-eslint/eslint-plugin'; -import tsParser from '@typescript-eslint/parser'; +import js from "@eslint/js"; +import typescript from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; export default [ js.configs.recommended, { - files: ['**/*.ts'], + files: ["**/*.ts"], languageOptions: { parser: tsParser, parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', + ecmaVersion: "latest", + sourceType: "module", }, globals: { - console: 'readonly', - process: 'readonly', - __dirname: 'readonly', - setTimeout: 'readonly', + console: "readonly", + process: "readonly", + __dirname: "readonly", + setTimeout: "readonly", }, }, plugins: { - '@typescript-eslint': typescript, + "@typescript-eslint": typescript, }, rules: { - ...typescript.configs['recommended'].rules, + ...typescript.configs["recommended"].rules, }, }, { - files: ['**/test/**/*.ts'], + files: ["**/test/**/*.ts"], languageOptions: { globals: { - suite: 'readonly', - test: 'readonly', - teardown: 'readonly', - suiteSetup: 'readonly', - suiteTeardown: 'readonly', - setup: 'readonly', + suite: "readonly", + test: "readonly", + teardown: "readonly", + suiteSetup: "readonly", + suiteTeardown: "readonly", + setup: "readonly", }, }, rules: { - '@typescript-eslint/no-unused-vars': 'off', + "@typescript-eslint/no-unused-vars": "off", }, }, ]; diff --git a/src/extension.ts b/src/extension.ts index 5006174..90e9431 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ -import * as vscode from 'vscode'; -import { activate as activateTaskProvider } from './providers/taskProvider'; +import * as vscode from "vscode"; +import { activate as activateTaskProvider } from "./providers/taskProvider"; export function activate(context: vscode.ExtensionContext) { activateTaskProvider(context); diff --git a/src/providers/taskProvider.ts b/src/providers/taskProvider.ts index 997856d..a612a36 100644 --- a/src/providers/taskProvider.ts +++ b/src/providers/taskProvider.ts @@ -1,6 +1,6 @@ -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; +import * as vscode from "vscode"; +import * as path from "path"; +import * as fs from "fs"; type TreeItemType = FolderItem | ModuleItem | TaskItem; @@ -8,11 +8,11 @@ class FolderItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: (FolderItem | ModuleItem)[] = [], - public readonly folderPath: string + public readonly folderPath: string, ) { super(label, vscode.TreeItemCollapsibleState.Expanded); - this.contextValue = 'folder'; - this.iconPath = new vscode.ThemeIcon('folder'); + this.contextValue = "folder"; + this.iconPath = new vscode.ThemeIcon("folder"); this.tooltip = folderPath; } } @@ -21,15 +21,15 @@ class ModuleItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: TaskItem[] = [], - public readonly filePath: string + public readonly filePath: string, ) { super(label, vscode.TreeItemCollapsibleState.Collapsed); - this.contextValue = 'module'; - this.iconPath = new vscode.ThemeIcon('symbol-file'); + this.contextValue = "module"; + this.iconPath = new vscode.ThemeIcon("symbol-file"); this.tooltip = filePath; this.command = { - command: 'vscode.open', - title: 'Open Module File', + command: "vscode.open", + title: "Open Module File", arguments: [vscode.Uri.file(filePath)], }; } @@ -39,22 +39,22 @@ class TaskItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly filePath: string, - public readonly lineNumber: number + public readonly lineNumber: number, ) { super(label, vscode.TreeItemCollapsibleState.None); - this.contextValue = 'task'; - this.iconPath = new vscode.ThemeIcon('symbol-method'); + this.contextValue = "task"; + this.iconPath = new vscode.ThemeIcon("symbol-method"); this.tooltip = `${this.label} - ${path.basename(this.filePath)}:${this.lineNumber}`; this.description = path.basename(this.filePath); this.command = { - command: 'vscode.open', - title: 'Open Task File', + command: "vscode.open", + title: "Open Task File", arguments: [ vscode.Uri.file(this.filePath), { selection: new vscode.Range( new vscode.Position(this.lineNumber - 1, 0), - new vscode.Position(this.lineNumber - 1, 0) + new vscode.Position(this.lineNumber - 1, 0), ), }, ], @@ -63,14 +63,17 @@ class TaskItem extends vscode.TreeItem { } export class PyTaskProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = - new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = - this._onDidChangeTreeData.event; + private _onDidChangeTreeData: vscode.EventEmitter< + TreeItemType | undefined | null | void + > = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event< + TreeItemType | undefined | null | void + > = this._onDidChangeTreeData.event; private fileSystemWatcher: vscode.FileSystemWatcher; constructor() { - this.fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/task_*.py'); + this.fileSystemWatcher = + vscode.workspace.createFileSystemWatcher("**/task_*.py"); this.fileSystemWatcher.onDidCreate(() => { this.refresh(); @@ -123,26 +126,32 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // Get all task modules across the workspace const taskFiles = await vscode.workspace.findFiles( - '**/task_*.py', - '{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}' + "**/task_*.py", + "{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}", ); // Process each task module for (const taskFile of taskFiles) { - const relativePath = path.relative(workspaceFolders[0].uri.fsPath, taskFile.fsPath); + const relativePath = path.relative( + workspaceFolders[0].uri.fsPath, + taskFile.fsPath, + ); const dirPath = path.dirname(relativePath); const fileName = path.basename(taskFile.fsPath); // Create folder hierarchy - let currentPath = ''; + let currentPath = ""; let currentItems = rootItems; const pathParts = dirPath.split(path.sep); // Skip if it's in the root - if (dirPath !== '.') { + if (dirPath !== ".") { for (const part of pathParts) { currentPath = currentPath ? path.join(currentPath, part) : part; - const fullPath = path.join(workspaceFolders[0].uri.fsPath, currentPath); + const fullPath = path.join( + workspaceFolders[0].uri.fsPath, + currentPath, + ); if (!currentItems.has(currentPath)) { const newFolder = new FolderItem(part, [], fullPath); @@ -154,19 +163,22 @@ export class PyTaskProvider implements vscode.TreeDataProvider { currentItems = new Map( folderItem.children .filter((child) => child instanceof FolderItem) - .map((child) => [path.basename(child.label), child as FolderItem]) + .map((child) => [ + path.basename(child.label), + child as FolderItem, + ]), ); } } } // Create module and its tasks - const content = fs.readFileSync(taskFile.fsPath, 'utf8'); + const content = fs.readFileSync(taskFile.fsPath, "utf8"); const taskItems = this.findTaskFunctions(taskFile.fsPath, content); const moduleItem = new ModuleItem(fileName, taskItems, taskFile.fsPath); // Add module to appropriate folder or root - if (dirPath === '.') { + if (dirPath === ".") { rootItems.set(fileName, moduleItem); } else { const parentFolder = rootItems.get(dirPath); @@ -197,8 +209,8 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // Booleans to track if the task decorator is imported as `from pytask import task` // and used as `@task` or `import pytask` and used as `@pytask.task`. let hasTaskImport = false; - let taskAlias = 'task'; // default name for 'from pytask import task' - let pytaskAlias = 'pytask'; // default name for 'import pytask' + let taskAlias = "task"; // default name for 'from pytask import task' + let pytaskAlias = "pytask"; // default name for 'import pytask' let hasPytaskImport = false; // Match the import statements @@ -208,9 +220,11 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // - from pytask import Product, task // - from pytask import (Product, task) const fromPytaskImport = content.match( - /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/ + /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/, + ); + const importPytask = content.match( + /import\s+pytask(?:\s+as\s+(\w+))?\s*$/m, ); - const importPytask = content.match(/import\s+pytask(?:\s+as\s+(\w+))?\s*$/m); if (fromPytaskImport) { hasTaskImport = true; @@ -222,19 +236,19 @@ export class PyTaskProvider implements vscode.TreeDataProvider { if (importPytask) { hasPytaskImport = true; // If there's an alias (import pytask as something), use it - pytaskAlias = importPytask[1] || 'pytask'; + pytaskAlias = importPytask[1] || "pytask"; } // Find the tasks. const tasks: TaskItem[] = []; - const lines = content.split('\n'); + const lines = content.split("\n"); let isDecorated = false; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Check for decorators - if (line.startsWith('@')) { + if (line.startsWith("@")) { // Handle both @task and @pytask.task(...) patterns isDecorated = (hasTaskImport && line === `@${taskAlias}`) || @@ -247,7 +261,7 @@ export class PyTaskProvider implements vscode.TreeDataProvider { if (funcMatch) { const funcName = funcMatch[1]; // Add if it's a task_* function or has a task decorator - if (funcName.startsWith('task_') || isDecorated) { + if (funcName.startsWith("task_") || isDecorated) { tasks.push(new TaskItem(funcName, filePath, i + 1)); } isDecorated = false; // Reset decorator flag @@ -261,18 +275,23 @@ export class PyTaskProvider implements vscode.TreeDataProvider { } } -export function activate(context: vscode.ExtensionContext): vscode.TreeView { +export function activate( + context: vscode.ExtensionContext, +): vscode.TreeView { const pytaskProvider = new PyTaskProvider(); - const treeView = vscode.window.createTreeView('pytaskExplorer', { + const treeView = vscode.window.createTreeView("pytaskExplorer", { treeDataProvider: pytaskProvider, showCollapseAll: true, }); context.subscriptions.push(treeView); - const refreshCommand = vscode.commands.registerCommand('pytask.refresh', () => { - pytaskProvider.refresh(); - }); + const refreshCommand = vscode.commands.registerCommand( + "pytask.refresh", + () => { + pytaskProvider.refresh(); + }, + ); context.subscriptions.push(refreshCommand, pytaskProvider); diff --git a/src/test/providers/index.ts b/src/test/providers/index.ts index fcaf7b1..700c0bf 100644 --- a/src/test/providers/index.ts +++ b/src/test/providers/index.ts @@ -1,20 +1,22 @@ -import * as path from 'path'; -import * as Mocha from 'mocha'; -import * as fs from 'fs'; +import * as path from "path"; +import * as Mocha from "mocha"; +import * as fs from "fs"; export function run(): Promise { // Create the mocha test const mocha = new Mocha({ - ui: 'tdd', + ui: "tdd", color: true, }); - const testsRoot = path.resolve(__dirname, '.'); + const testsRoot = path.resolve(__dirname, "."); return new Promise((resolve, reject) => { try { // Get all test files - const files = fs.readdirSync(testsRoot).filter((file) => file.endsWith('.test.js')); + const files = fs + .readdirSync(testsRoot) + .filter((file) => file.endsWith(".test.js")); // Add files to the test suite files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); diff --git a/src/test/providers/taskProvider.test.ts b/src/test/providers/taskProvider.test.ts index 93e44fa..d4f1254 100644 --- a/src/test/providers/taskProvider.test.ts +++ b/src/test/providers/taskProvider.test.ts @@ -1,11 +1,11 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; -import { expect } from 'chai'; -import { PyTaskProvider } from '../../providers/taskProvider'; - -suite('PyTask Extension Test Suite', function () { +import * as assert from "assert"; +import * as vscode from "vscode"; +import * as path from "path"; +import * as fs from "fs"; +import { expect } from "chai"; +import { PyTaskProvider } from "../../providers/taskProvider"; + +suite("PyTask Extension Test Suite", function () { // Increase timeout for all tests this.timeout(5000); @@ -14,9 +14,9 @@ suite('PyTask Extension Test Suite', function () { suiteSetup(function () { const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders) { - throw new Error('No workspace folders found'); + throw new Error("No workspace folders found"); } - testFilePath = path.join(workspaceFolders[0].uri.fsPath, 'task_test.py'); + testFilePath = path.join(workspaceFolders[0].uri.fsPath, "task_test.py"); }); setup(async function () { @@ -43,16 +43,18 @@ def not_a_task(): } }); - test('Extension should be present', async function () { - const extension = vscode.extensions.getExtension('undefined_publisher.pytask-vscode'); - assert.ok(extension, 'Extension should be present'); + test("Extension should be present", async function () { + const extension = vscode.extensions.getExtension( + "undefined_publisher.pytask-vscode", + ); + assert.ok(extension, "Extension should be present"); await extension?.activate(); }); - test('Should find task functions in Python files', async function () { + test("Should find task functions in Python files", async function () { // Get the PyTask explorer view const provider = new PyTaskProvider(); - const treeView = vscode.window.createTreeView('pytaskExplorer', { + const treeView = vscode.window.createTreeView("pytaskExplorer", { treeDataProvider: provider, }); @@ -61,39 +63,51 @@ def not_a_task(): const items = await provider.getChildren(); // Verify we found the correct number of modules - expect(items.length).to.equal(1, 'Should find exactly 1 module'); + expect(items.length).to.equal(1, "Should find exactly 1 module"); const moduleItem = items[0]; - expect(moduleItem.contextValue).to.equal('module', 'First item should be a module'); - expect(moduleItem.label).to.equal('task_test.py', 'Module should have correct name'); + expect(moduleItem.contextValue).to.equal( + "module", + "First item should be a module", + ); + expect(moduleItem.label).to.equal( + "task_test.py", + "Module should have correct name", + ); // Verify tasks within the module const tasks = await provider.getChildren(moduleItem); - expect(tasks.length).to.equal(2, 'Should find exactly 2 tasks in the module'); + expect(tasks.length).to.equal( + 2, + "Should find exactly 2 tasks in the module", + ); // Verify task names const taskNames = tasks.map((item: vscode.TreeItem) => item.label); - expect(taskNames).to.include('task_one', 'Should find task_one'); - expect(taskNames).to.include('task_two', 'Should find task_two'); - expect(taskNames).to.not.include('not_a_task', 'Should not find not_a_task'); + expect(taskNames).to.include("task_one", "Should find task_one"); + expect(taskNames).to.include("task_two", "Should find task_two"); + expect(taskNames).to.not.include( + "not_a_task", + "Should not find not_a_task", + ); } finally { treeView.dispose(); } }); - test('Should display empty task modules', async function () { + test("Should display empty task modules", async function () { const wsfolders = vscode.workspace.workspaceFolders; if (!wsfolders) { - throw new Error('No workspace folders found'); + throw new Error("No workspace folders found"); } // Create an empty task file - const emptyTaskFile = path.join(wsfolders[0].uri.fsPath, 'task_empty.py'); - fs.writeFileSync(emptyTaskFile, '# Empty task file\n'); + const emptyTaskFile = path.join(wsfolders[0].uri.fsPath, "task_empty.py"); + fs.writeFileSync(emptyTaskFile, "# Empty task file\n"); try { const provider = new PyTaskProvider(); - const treeView = vscode.window.createTreeView('pytaskExplorer', { + const treeView = vscode.window.createTreeView("pytaskExplorer", { treeDataProvider: provider, }); @@ -102,19 +116,24 @@ def not_a_task(): const items = await provider.getChildren(); // Verify we found both modules (empty and non-empty) - expect(items.length).to.equal(2, 'Should find both task modules'); + expect(items.length).to.equal(2, "Should find both task modules"); // Find the empty module - const emptyModule = items.find((item: vscode.TreeItem) => item.label === 'task_empty.py'); + const emptyModule = items.find( + (item: vscode.TreeItem) => item.label === "task_empty.py", + ); expect(emptyModule).to.exist; expect(emptyModule!.contextValue).to.equal( - 'module', - 'Empty file should be shown as module' + "module", + "Empty file should be shown as module", ); // Verify empty module has no tasks const emptyModuleTasks = await provider.getChildren(emptyModule); - expect(emptyModuleTasks.length).to.equal(0, 'Empty module should have no tasks'); + expect(emptyModuleTasks.length).to.equal( + 0, + "Empty module should have no tasks", + ); } finally { treeView.dispose(); } @@ -126,10 +145,10 @@ def not_a_task(): } }); - test('Should update when task file changes', async function () { + test("Should update when task file changes", async function () { // Add a new task to the file const updatedContent = - fs.readFileSync(testFilePath, 'utf8') + '\ndef task_three():\n pass\n'; + fs.readFileSync(testFilePath, "utf8") + "\ndef task_three():\n pass\n"; fs.writeFileSync(testFilePath, updatedContent); // Wait for the file watcher to detect changes @@ -137,7 +156,7 @@ def not_a_task(): // Get the tree view items const provider = new PyTaskProvider(); - const treeView = vscode.window.createTreeView('pytaskExplorer', { + const treeView = vscode.window.createTreeView("pytaskExplorer", { treeDataProvider: provider, }); @@ -145,30 +164,39 @@ def not_a_task(): const items = await provider.getChildren(); // Verify we still have one module - expect(items.length).to.equal(1, 'Should find exactly 1 module'); + expect(items.length).to.equal(1, "Should find exactly 1 module"); const moduleItem = items[0]; - expect(moduleItem.contextValue).to.equal('module', 'First item should be a module'); + expect(moduleItem.contextValue).to.equal( + "module", + "First item should be a module", + ); // Get tasks under the module const tasks = await provider.getChildren(moduleItem); - expect(tasks.length).to.equal(3, 'Should find exactly 3 tasks in the module'); + expect(tasks.length).to.equal( + 3, + "Should find exactly 3 tasks in the module", + ); // Verify task names const taskNames = tasks.map((item: vscode.TreeItem) => item.label); - expect(taskNames).to.include('task_one', 'Should find task_one'); - expect(taskNames).to.include('task_two', 'Should find task_two'); - expect(taskNames).to.include('task_three', 'Should find the newly added task_three'); + expect(taskNames).to.include("task_one", "Should find task_one"); + expect(taskNames).to.include("task_two", "Should find task_two"); + expect(taskNames).to.include( + "task_three", + "Should find the newly added task_three", + ); } finally { treeView.dispose(); } }); }); -suite('Task Function Detection', () => { +suite("Task Function Detection", () => { const provider = new PyTaskProvider(); - const dummyPath = '/test/task_test.py'; + const dummyPath = "/test/task_test.py"; - test('Should find functions with task_ prefix', () => { + test("Should find functions with task_ prefix", () => { const content = ` def task_one(): pass @@ -180,12 +208,12 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(2, 'Should find exactly 2 tasks'); - expect(tasks[0].label).to.equal('task_one'); - expect(tasks[1].label).to.equal('task_two'); + expect(tasks).to.have.lengthOf(2, "Should find exactly 2 tasks"); + expect(tasks[0].label).to.equal("task_one"); + expect(tasks[1].label).to.equal("task_two"); }); - test('Should find functions with @task decorator', () => { + test("Should find functions with @task decorator", () => { const content = ` from pytask import task @@ -201,12 +229,12 @@ def not_decorated(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(2, 'Should find exactly 2 tasks'); - expect(tasks[0].label).to.equal('another_function'); - expect(tasks[1].label).to.equal('function_one'); + expect(tasks).to.have.lengthOf(2, "Should find exactly 2 tasks"); + expect(tasks[0].label).to.equal("another_function"); + expect(tasks[1].label).to.equal("function_one"); }); - test('Should find functions with @pytask.task decorator', () => { + test("Should find functions with @pytask.task decorator", () => { const content = ` import pytask @@ -223,13 +251,13 @@ def function_three(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(3, 'Should find exactly 3 tasks'); - expect(tasks[0].label).to.equal('function_one'); - expect(tasks[1].label).to.equal('function_three'); - expect(tasks[2].label).to.equal('function_two'); + expect(tasks).to.have.lengthOf(3, "Should find exactly 3 tasks"); + expect(tasks[0].label).to.equal("function_one"); + expect(tasks[1].label).to.equal("function_three"); + expect(tasks[2].label).to.equal("function_two"); }); - test('Should handle mixed task definitions', () => { + test("Should handle mixed task definitions", () => { const content = ` from pytask import task import pytask @@ -249,13 +277,13 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(3, 'Should find exactly 3 tasks'); - expect(tasks[0].label).to.equal('function_three'); - expect(tasks[1].label).to.equal('function_two'); - expect(tasks[2].label).to.equal('task_one'); + expect(tasks).to.have.lengthOf(3, "Should find exactly 3 tasks"); + expect(tasks[0].label).to.equal("function_three"); + expect(tasks[1].label).to.equal("function_two"); + expect(tasks[2].label).to.equal("task_one"); }); - test('Should not find tasks without proper imports', () => { + test("Should not find tasks without proper imports", () => { const content = ` # Missing imports @@ -271,11 +299,14 @@ def task_one(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(1, 'Should only find the task_ prefixed function'); - expect(tasks[0].label).to.equal('task_one'); + expect(tasks).to.have.lengthOf( + 1, + "Should only find the task_ prefixed function", + ); + expect(tasks[0].label).to.equal("task_one"); }); - test('Should handle complex import statements', () => { + test("Should handle complex import statements", () => { const content = ` from pytask import clean, task, collect import pytask as pt @@ -289,12 +320,12 @@ def another_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(2, 'Should find both tasks'); - expect(tasks[0].label).to.equal('another_task'); - expect(tasks[1].label).to.equal('task_one'); + expect(tasks).to.have.lengthOf(2, "Should find both tasks"); + expect(tasks[0].label).to.equal("another_task"); + expect(tasks[1].label).to.equal("task_one"); }); - test('Should handle aliased task import', () => { + test("Should handle aliased task import", () => { const content = ` from pytask import task as t @@ -306,11 +337,14 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(1, 'Should find task with aliased decorator'); - expect(tasks[0].label).to.equal('my_task'); + expect(tasks).to.have.lengthOf( + 1, + "Should find task with aliased decorator", + ); + expect(tasks[0].label).to.equal("my_task"); }); - test('Should handle multi-import statements', () => { + test("Should handle multi-import statements", () => { const content = ` from pytask import Product, task @@ -322,11 +356,11 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(1, 'Should find task with multi-import'); - expect(tasks[0].label).to.equal('task_one'); + expect(tasks).to.have.lengthOf(1, "Should find task with multi-import"); + expect(tasks[0].label).to.equal("task_one"); }); - test('Should handle multi-line import statements', () => { + test("Should handle multi-line import statements", () => { const content = ` from pytask import ( Product, @@ -341,11 +375,14 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(1, 'Should find task with multi-line import'); - expect(tasks[0].label).to.equal('task_one'); + expect(tasks).to.have.lengthOf( + 1, + "Should find task with multi-line import", + ); + expect(tasks[0].label).to.equal("task_one"); }); - test('Should set correct line numbers', () => { + test("Should set correct line numbers", () => { const content = ` from pytask import task @@ -357,10 +394,10 @@ def decorated_task(): # line 8 pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(2, 'Should find both tasks'); - expect(tasks[0].label).to.equal('decorated_task'); + expect(tasks).to.have.lengthOf(2, "Should find both tasks"); + expect(tasks[0].label).to.equal("decorated_task"); expect(tasks[0].lineNumber).to.equal(8); - expect(tasks[1].label).to.equal('task_one'); + expect(tasks[1].label).to.equal("task_one"); expect(tasks[1].lineNumber).to.equal(4); }); }); diff --git a/src/test/runTest.ts b/src/test/runTest.ts index 333c99e..ee25e65 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -1,23 +1,23 @@ -import * as path from 'path'; -import { runTests } from '@vscode/test-electron'; -import * as fs from 'fs'; -import * as os from 'os'; +import * as path from "path"; +import { runTests } from "@vscode/test-electron"; +import * as fs from "fs"; +import * as os from "os"; async function main() { try { // Create a temporary test workspace const testWorkspacePath = path.join( os.tmpdir(), - `pytask-test-${Math.random().toString(36).substring(2)}` + `pytask-test-${Math.random().toString(36).substring(2)}`, ); fs.mkdirSync(testWorkspacePath, { recursive: true }); console.log(`Test workspace created at: ${testWorkspacePath}`); // The folder containing the Extension Manifest package.json - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); // The path to the extension test script - const extensionTestsPath = path.resolve(__dirname, './providers/index'); + const extensionTestsPath = path.resolve(__dirname, "./providers/index"); // Download VS Code, unzip it and run the integration test await runTests({ @@ -25,8 +25,8 @@ async function main() { extensionTestsPath, launchArgs: [ testWorkspacePath, - '--disable-extensions', // Disable other extensions - '--disable-workspace-trust', // Disable workspace trust dialog + "--disable-extensions", // Disable other extensions + "--disable-workspace-trust", // Disable workspace trust dialog ], }); @@ -36,7 +36,7 @@ async function main() { } console.log(`Test workspace cleaned up: ${testWorkspacePath}`); } catch (err) { - console.error('Failed to run tests'); + console.error("Failed to run tests"); process.exit(1); } } From 7b589f79cd0bf7449cfd102bb509883a9153eab4 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Fri, 3 Jan 2025 22:32:41 +0100 Subject: [PATCH 4/9] fix. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c6bba8..cb9b1a7 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "vscode:prepublish": "npm run compile", "compile": "tsc -p ./", "watch": "tsc -watch -p ./", - "pretest": "npm run compile && npm run lint && npm run format", + "pretest": "rm -rf out && npm run compile && npm run lint && npm run format", "lint": "eslint src", "test": "node ./out/test/runTest.js", "format": "prettier --write \"src/**/*.{ts,js,json,md}\"" From 61756141e09033a055288c497e1d0ef82d2c9160 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Fri, 3 Jan 2025 22:36:24 +0100 Subject: [PATCH 5/9] Fix. --- src/providers/taskProvider.ts | 57 ++++++++-------------- src/test/providers/index.ts | 4 +- src/test/providers/taskProvider.test.ts | 65 ++++++------------------- src/test/runTest.ts | 2 +- 4 files changed, 35 insertions(+), 93 deletions(-) diff --git a/src/providers/taskProvider.ts b/src/providers/taskProvider.ts index a612a36..de71e12 100644 --- a/src/providers/taskProvider.ts +++ b/src/providers/taskProvider.ts @@ -8,7 +8,7 @@ class FolderItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: (FolderItem | ModuleItem)[] = [], - public readonly folderPath: string, + public readonly folderPath: string ) { super(label, vscode.TreeItemCollapsibleState.Expanded); this.contextValue = "folder"; @@ -21,7 +21,7 @@ class ModuleItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: TaskItem[] = [], - public readonly filePath: string, + public readonly filePath: string ) { super(label, vscode.TreeItemCollapsibleState.Collapsed); this.contextValue = "module"; @@ -39,7 +39,7 @@ class TaskItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly filePath: string, - public readonly lineNumber: number, + public readonly lineNumber: number ) { super(label, vscode.TreeItemCollapsibleState.None); this.contextValue = "task"; @@ -54,7 +54,7 @@ class TaskItem extends vscode.TreeItem { { selection: new vscode.Range( new vscode.Position(this.lineNumber - 1, 0), - new vscode.Position(this.lineNumber - 1, 0), + new vscode.Position(this.lineNumber - 1, 0) ), }, ], @@ -63,17 +63,14 @@ class TaskItem extends vscode.TreeItem { } export class PyTaskProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter< - TreeItemType | undefined | null | void - > = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event< - TreeItemType | undefined | null | void - > = this._onDidChangeTreeData.event; + private _onDidChangeTreeData: vscode.EventEmitter = + new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = + this._onDidChangeTreeData.event; private fileSystemWatcher: vscode.FileSystemWatcher; constructor() { - this.fileSystemWatcher = - vscode.workspace.createFileSystemWatcher("**/task_*.py"); + this.fileSystemWatcher = vscode.workspace.createFileSystemWatcher("**/task_*.py"); this.fileSystemWatcher.onDidCreate(() => { this.refresh(); @@ -127,15 +124,12 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // Get all task modules across the workspace const taskFiles = await vscode.workspace.findFiles( "**/task_*.py", - "{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}", + "{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}" ); // Process each task module for (const taskFile of taskFiles) { - const relativePath = path.relative( - workspaceFolders[0].uri.fsPath, - taskFile.fsPath, - ); + const relativePath = path.relative(workspaceFolders[0].uri.fsPath, taskFile.fsPath); const dirPath = path.dirname(relativePath); const fileName = path.basename(taskFile.fsPath); @@ -148,10 +142,7 @@ export class PyTaskProvider implements vscode.TreeDataProvider { if (dirPath !== ".") { for (const part of pathParts) { currentPath = currentPath ? path.join(currentPath, part) : part; - const fullPath = path.join( - workspaceFolders[0].uri.fsPath, - currentPath, - ); + const fullPath = path.join(workspaceFolders[0].uri.fsPath, currentPath); if (!currentItems.has(currentPath)) { const newFolder = new FolderItem(part, [], fullPath); @@ -163,10 +154,7 @@ export class PyTaskProvider implements vscode.TreeDataProvider { currentItems = new Map( folderItem.children .filter((child) => child instanceof FolderItem) - .map((child) => [ - path.basename(child.label), - child as FolderItem, - ]), + .map((child) => [path.basename(child.label), child as FolderItem]) ); } } @@ -220,11 +208,9 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // - from pytask import Product, task // - from pytask import (Product, task) const fromPytaskImport = content.match( - /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/, - ); - const importPytask = content.match( - /import\s+pytask(?:\s+as\s+(\w+))?\s*$/m, + /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/ ); + const importPytask = content.match(/import\s+pytask(?:\s+as\s+(\w+))?\s*$/m); if (fromPytaskImport) { hasTaskImport = true; @@ -275,9 +261,7 @@ export class PyTaskProvider implements vscode.TreeDataProvider { } } -export function activate( - context: vscode.ExtensionContext, -): vscode.TreeView { +export function activate(context: vscode.ExtensionContext): vscode.TreeView { const pytaskProvider = new PyTaskProvider(); const treeView = vscode.window.createTreeView("pytaskExplorer", { treeDataProvider: pytaskProvider, @@ -286,12 +270,9 @@ export function activate( context.subscriptions.push(treeView); - const refreshCommand = vscode.commands.registerCommand( - "pytask.refresh", - () => { - pytaskProvider.refresh(); - }, - ); + const refreshCommand = vscode.commands.registerCommand("pytask.refresh", () => { + pytaskProvider.refresh(); + }); context.subscriptions.push(refreshCommand, pytaskProvider); diff --git a/src/test/providers/index.ts b/src/test/providers/index.ts index 700c0bf..b39b7a2 100644 --- a/src/test/providers/index.ts +++ b/src/test/providers/index.ts @@ -14,9 +14,7 @@ export function run(): Promise { return new Promise((resolve, reject) => { try { // Get all test files - const files = fs - .readdirSync(testsRoot) - .filter((file) => file.endsWith(".test.js")); + const files = fs.readdirSync(testsRoot).filter((file) => file.endsWith(".test.js")); // Add files to the test suite files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); diff --git a/src/test/providers/taskProvider.test.ts b/src/test/providers/taskProvider.test.ts index d4f1254..69b97aa 100644 --- a/src/test/providers/taskProvider.test.ts +++ b/src/test/providers/taskProvider.test.ts @@ -44,9 +44,7 @@ def not_a_task(): }); test("Extension should be present", async function () { - const extension = vscode.extensions.getExtension( - "undefined_publisher.pytask-vscode", - ); + const extension = vscode.extensions.getExtension("undefined_publisher.pytask-vscode"); assert.ok(extension, "Extension should be present"); await extension?.activate(); }); @@ -66,30 +64,18 @@ def not_a_task(): expect(items.length).to.equal(1, "Should find exactly 1 module"); const moduleItem = items[0]; - expect(moduleItem.contextValue).to.equal( - "module", - "First item should be a module", - ); - expect(moduleItem.label).to.equal( - "task_test.py", - "Module should have correct name", - ); + expect(moduleItem.contextValue).to.equal("module", "First item should be a module"); + expect(moduleItem.label).to.equal("task_test.py", "Module should have correct name"); // Verify tasks within the module const tasks = await provider.getChildren(moduleItem); - expect(tasks.length).to.equal( - 2, - "Should find exactly 2 tasks in the module", - ); + expect(tasks.length).to.equal(2, "Should find exactly 2 tasks in the module"); // Verify task names const taskNames = tasks.map((item: vscode.TreeItem) => item.label); expect(taskNames).to.include("task_one", "Should find task_one"); expect(taskNames).to.include("task_two", "Should find task_two"); - expect(taskNames).to.not.include( - "not_a_task", - "Should not find not_a_task", - ); + expect(taskNames).to.not.include("not_a_task", "Should not find not_a_task"); } finally { treeView.dispose(); } @@ -119,21 +105,16 @@ def not_a_task(): expect(items.length).to.equal(2, "Should find both task modules"); // Find the empty module - const emptyModule = items.find( - (item: vscode.TreeItem) => item.label === "task_empty.py", - ); + const emptyModule = items.find((item: vscode.TreeItem) => item.label === "task_empty.py"); expect(emptyModule).to.exist; expect(emptyModule!.contextValue).to.equal( "module", - "Empty file should be shown as module", + "Empty file should be shown as module" ); // Verify empty module has no tasks const emptyModuleTasks = await provider.getChildren(emptyModule); - expect(emptyModuleTasks.length).to.equal( - 0, - "Empty module should have no tasks", - ); + expect(emptyModuleTasks.length).to.equal(0, "Empty module should have no tasks"); } finally { treeView.dispose(); } @@ -166,26 +147,17 @@ def not_a_task(): // Verify we still have one module expect(items.length).to.equal(1, "Should find exactly 1 module"); const moduleItem = items[0]; - expect(moduleItem.contextValue).to.equal( - "module", - "First item should be a module", - ); + expect(moduleItem.contextValue).to.equal("module", "First item should be a module"); // Get tasks under the module const tasks = await provider.getChildren(moduleItem); - expect(tasks.length).to.equal( - 3, - "Should find exactly 3 tasks in the module", - ); + expect(tasks.length).to.equal(3, "Should find exactly 3 tasks in the module"); // Verify task names const taskNames = tasks.map((item: vscode.TreeItem) => item.label); expect(taskNames).to.include("task_one", "Should find task_one"); expect(taskNames).to.include("task_two", "Should find task_two"); - expect(taskNames).to.include( - "task_three", - "Should find the newly added task_three", - ); + expect(taskNames).to.include("task_three", "Should find the newly added task_three"); } finally { treeView.dispose(); } @@ -299,10 +271,7 @@ def task_one(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf( - 1, - "Should only find the task_ prefixed function", - ); + expect(tasks).to.have.lengthOf(1, "Should only find the task_ prefixed function"); expect(tasks[0].label).to.equal("task_one"); }); @@ -337,10 +306,7 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf( - 1, - "Should find task with aliased decorator", - ); + expect(tasks).to.have.lengthOf(1, "Should find task with aliased decorator"); expect(tasks[0].label).to.equal("my_task"); }); @@ -375,10 +341,7 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf( - 1, - "Should find task with multi-line import", - ); + expect(tasks).to.have.lengthOf(1, "Should find task with multi-line import"); expect(tasks[0].label).to.equal("task_one"); }); diff --git a/src/test/runTest.ts b/src/test/runTest.ts index ee25e65..4db6b4e 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -8,7 +8,7 @@ async function main() { // Create a temporary test workspace const testWorkspacePath = path.join( os.tmpdir(), - `pytask-test-${Math.random().toString(36).substring(2)}`, + `pytask-test-${Math.random().toString(36).substring(2)}` ); fs.mkdirSync(testWorkspacePath, { recursive: true }); console.log(`Test workspace created at: ${testWorkspacePath}`); From 00535a93aa1b8caa2c42b9da20cf364fd77b350d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 21:36:36 +0000 Subject: [PATCH 6/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/providers/taskProvider.ts | 57 ++++++++++++++-------- src/test/providers/index.ts | 4 +- src/test/providers/taskProvider.test.ts | 65 +++++++++++++++++++------ src/test/runTest.ts | 2 +- 4 files changed, 93 insertions(+), 35 deletions(-) diff --git a/src/providers/taskProvider.ts b/src/providers/taskProvider.ts index de71e12..a612a36 100644 --- a/src/providers/taskProvider.ts +++ b/src/providers/taskProvider.ts @@ -8,7 +8,7 @@ class FolderItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: (FolderItem | ModuleItem)[] = [], - public readonly folderPath: string + public readonly folderPath: string, ) { super(label, vscode.TreeItemCollapsibleState.Expanded); this.contextValue = "folder"; @@ -21,7 +21,7 @@ class ModuleItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: TaskItem[] = [], - public readonly filePath: string + public readonly filePath: string, ) { super(label, vscode.TreeItemCollapsibleState.Collapsed); this.contextValue = "module"; @@ -39,7 +39,7 @@ class TaskItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly filePath: string, - public readonly lineNumber: number + public readonly lineNumber: number, ) { super(label, vscode.TreeItemCollapsibleState.None); this.contextValue = "task"; @@ -54,7 +54,7 @@ class TaskItem extends vscode.TreeItem { { selection: new vscode.Range( new vscode.Position(this.lineNumber - 1, 0), - new vscode.Position(this.lineNumber - 1, 0) + new vscode.Position(this.lineNumber - 1, 0), ), }, ], @@ -63,14 +63,17 @@ class TaskItem extends vscode.TreeItem { } export class PyTaskProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = - new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = - this._onDidChangeTreeData.event; + private _onDidChangeTreeData: vscode.EventEmitter< + TreeItemType | undefined | null | void + > = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event< + TreeItemType | undefined | null | void + > = this._onDidChangeTreeData.event; private fileSystemWatcher: vscode.FileSystemWatcher; constructor() { - this.fileSystemWatcher = vscode.workspace.createFileSystemWatcher("**/task_*.py"); + this.fileSystemWatcher = + vscode.workspace.createFileSystemWatcher("**/task_*.py"); this.fileSystemWatcher.onDidCreate(() => { this.refresh(); @@ -124,12 +127,15 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // Get all task modules across the workspace const taskFiles = await vscode.workspace.findFiles( "**/task_*.py", - "{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}" + "{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}", ); // Process each task module for (const taskFile of taskFiles) { - const relativePath = path.relative(workspaceFolders[0].uri.fsPath, taskFile.fsPath); + const relativePath = path.relative( + workspaceFolders[0].uri.fsPath, + taskFile.fsPath, + ); const dirPath = path.dirname(relativePath); const fileName = path.basename(taskFile.fsPath); @@ -142,7 +148,10 @@ export class PyTaskProvider implements vscode.TreeDataProvider { if (dirPath !== ".") { for (const part of pathParts) { currentPath = currentPath ? path.join(currentPath, part) : part; - const fullPath = path.join(workspaceFolders[0].uri.fsPath, currentPath); + const fullPath = path.join( + workspaceFolders[0].uri.fsPath, + currentPath, + ); if (!currentItems.has(currentPath)) { const newFolder = new FolderItem(part, [], fullPath); @@ -154,7 +163,10 @@ export class PyTaskProvider implements vscode.TreeDataProvider { currentItems = new Map( folderItem.children .filter((child) => child instanceof FolderItem) - .map((child) => [path.basename(child.label), child as FolderItem]) + .map((child) => [ + path.basename(child.label), + child as FolderItem, + ]), ); } } @@ -208,9 +220,11 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // - from pytask import Product, task // - from pytask import (Product, task) const fromPytaskImport = content.match( - /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/ + /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/, + ); + const importPytask = content.match( + /import\s+pytask(?:\s+as\s+(\w+))?\s*$/m, ); - const importPytask = content.match(/import\s+pytask(?:\s+as\s+(\w+))?\s*$/m); if (fromPytaskImport) { hasTaskImport = true; @@ -261,7 +275,9 @@ export class PyTaskProvider implements vscode.TreeDataProvider { } } -export function activate(context: vscode.ExtensionContext): vscode.TreeView { +export function activate( + context: vscode.ExtensionContext, +): vscode.TreeView { const pytaskProvider = new PyTaskProvider(); const treeView = vscode.window.createTreeView("pytaskExplorer", { treeDataProvider: pytaskProvider, @@ -270,9 +286,12 @@ export function activate(context: vscode.ExtensionContext): vscode.TreeView { - pytaskProvider.refresh(); - }); + const refreshCommand = vscode.commands.registerCommand( + "pytask.refresh", + () => { + pytaskProvider.refresh(); + }, + ); context.subscriptions.push(refreshCommand, pytaskProvider); diff --git a/src/test/providers/index.ts b/src/test/providers/index.ts index b39b7a2..700c0bf 100644 --- a/src/test/providers/index.ts +++ b/src/test/providers/index.ts @@ -14,7 +14,9 @@ export function run(): Promise { return new Promise((resolve, reject) => { try { // Get all test files - const files = fs.readdirSync(testsRoot).filter((file) => file.endsWith(".test.js")); + const files = fs + .readdirSync(testsRoot) + .filter((file) => file.endsWith(".test.js")); // Add files to the test suite files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); diff --git a/src/test/providers/taskProvider.test.ts b/src/test/providers/taskProvider.test.ts index 69b97aa..d4f1254 100644 --- a/src/test/providers/taskProvider.test.ts +++ b/src/test/providers/taskProvider.test.ts @@ -44,7 +44,9 @@ def not_a_task(): }); test("Extension should be present", async function () { - const extension = vscode.extensions.getExtension("undefined_publisher.pytask-vscode"); + const extension = vscode.extensions.getExtension( + "undefined_publisher.pytask-vscode", + ); assert.ok(extension, "Extension should be present"); await extension?.activate(); }); @@ -64,18 +66,30 @@ def not_a_task(): expect(items.length).to.equal(1, "Should find exactly 1 module"); const moduleItem = items[0]; - expect(moduleItem.contextValue).to.equal("module", "First item should be a module"); - expect(moduleItem.label).to.equal("task_test.py", "Module should have correct name"); + expect(moduleItem.contextValue).to.equal( + "module", + "First item should be a module", + ); + expect(moduleItem.label).to.equal( + "task_test.py", + "Module should have correct name", + ); // Verify tasks within the module const tasks = await provider.getChildren(moduleItem); - expect(tasks.length).to.equal(2, "Should find exactly 2 tasks in the module"); + expect(tasks.length).to.equal( + 2, + "Should find exactly 2 tasks in the module", + ); // Verify task names const taskNames = tasks.map((item: vscode.TreeItem) => item.label); expect(taskNames).to.include("task_one", "Should find task_one"); expect(taskNames).to.include("task_two", "Should find task_two"); - expect(taskNames).to.not.include("not_a_task", "Should not find not_a_task"); + expect(taskNames).to.not.include( + "not_a_task", + "Should not find not_a_task", + ); } finally { treeView.dispose(); } @@ -105,16 +119,21 @@ def not_a_task(): expect(items.length).to.equal(2, "Should find both task modules"); // Find the empty module - const emptyModule = items.find((item: vscode.TreeItem) => item.label === "task_empty.py"); + const emptyModule = items.find( + (item: vscode.TreeItem) => item.label === "task_empty.py", + ); expect(emptyModule).to.exist; expect(emptyModule!.contextValue).to.equal( "module", - "Empty file should be shown as module" + "Empty file should be shown as module", ); // Verify empty module has no tasks const emptyModuleTasks = await provider.getChildren(emptyModule); - expect(emptyModuleTasks.length).to.equal(0, "Empty module should have no tasks"); + expect(emptyModuleTasks.length).to.equal( + 0, + "Empty module should have no tasks", + ); } finally { treeView.dispose(); } @@ -147,17 +166,26 @@ def not_a_task(): // Verify we still have one module expect(items.length).to.equal(1, "Should find exactly 1 module"); const moduleItem = items[0]; - expect(moduleItem.contextValue).to.equal("module", "First item should be a module"); + expect(moduleItem.contextValue).to.equal( + "module", + "First item should be a module", + ); // Get tasks under the module const tasks = await provider.getChildren(moduleItem); - expect(tasks.length).to.equal(3, "Should find exactly 3 tasks in the module"); + expect(tasks.length).to.equal( + 3, + "Should find exactly 3 tasks in the module", + ); // Verify task names const taskNames = tasks.map((item: vscode.TreeItem) => item.label); expect(taskNames).to.include("task_one", "Should find task_one"); expect(taskNames).to.include("task_two", "Should find task_two"); - expect(taskNames).to.include("task_three", "Should find the newly added task_three"); + expect(taskNames).to.include( + "task_three", + "Should find the newly added task_three", + ); } finally { treeView.dispose(); } @@ -271,7 +299,10 @@ def task_one(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(1, "Should only find the task_ prefixed function"); + expect(tasks).to.have.lengthOf( + 1, + "Should only find the task_ prefixed function", + ); expect(tasks[0].label).to.equal("task_one"); }); @@ -306,7 +337,10 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(1, "Should find task with aliased decorator"); + expect(tasks).to.have.lengthOf( + 1, + "Should find task with aliased decorator", + ); expect(tasks[0].label).to.equal("my_task"); }); @@ -341,7 +375,10 @@ def not_a_task(): pass `; const tasks = provider.findTaskFunctions(dummyPath, content); - expect(tasks).to.have.lengthOf(1, "Should find task with multi-line import"); + expect(tasks).to.have.lengthOf( + 1, + "Should find task with multi-line import", + ); expect(tasks[0].label).to.equal("task_one"); }); diff --git a/src/test/runTest.ts b/src/test/runTest.ts index 4db6b4e..ee25e65 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -8,7 +8,7 @@ async function main() { // Create a temporary test workspace const testWorkspacePath = path.join( os.tmpdir(), - `pytask-test-${Math.random().toString(36).substring(2)}` + `pytask-test-${Math.random().toString(36).substring(2)}`, ); fs.mkdirSync(testWorkspacePath, { recursive: true }); console.log(`Test workspace created at: ${testWorkspacePath}`); From 8338c31188132b471ce9e47c14c81c791d599103 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Fri, 3 Jan 2025 22:39:02 +0100 Subject: [PATCH 7/9] Commit prettier config. --- .prettierrc | 3 +++ src/providers/taskProvider.ts | 14 +++++++------- src/test/providers/taskProvider.test.ts | 2 +- src/test/runTest.ts | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..de753c5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 100 +} diff --git a/src/providers/taskProvider.ts b/src/providers/taskProvider.ts index de71e12..d0753cb 100644 --- a/src/providers/taskProvider.ts +++ b/src/providers/taskProvider.ts @@ -8,7 +8,7 @@ class FolderItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: (FolderItem | ModuleItem)[] = [], - public readonly folderPath: string + public readonly folderPath: string, ) { super(label, vscode.TreeItemCollapsibleState.Expanded); this.contextValue = "folder"; @@ -21,7 +21,7 @@ class ModuleItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly children: TaskItem[] = [], - public readonly filePath: string + public readonly filePath: string, ) { super(label, vscode.TreeItemCollapsibleState.Collapsed); this.contextValue = "module"; @@ -39,7 +39,7 @@ class TaskItem extends vscode.TreeItem { constructor( public readonly label: string, public readonly filePath: string, - public readonly lineNumber: number + public readonly lineNumber: number, ) { super(label, vscode.TreeItemCollapsibleState.None); this.contextValue = "task"; @@ -54,7 +54,7 @@ class TaskItem extends vscode.TreeItem { { selection: new vscode.Range( new vscode.Position(this.lineNumber - 1, 0), - new vscode.Position(this.lineNumber - 1, 0) + new vscode.Position(this.lineNumber - 1, 0), ), }, ], @@ -124,7 +124,7 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // Get all task modules across the workspace const taskFiles = await vscode.workspace.findFiles( "**/task_*.py", - "{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}" + "{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}", ); // Process each task module @@ -154,7 +154,7 @@ export class PyTaskProvider implements vscode.TreeDataProvider { currentItems = new Map( folderItem.children .filter((child) => child instanceof FolderItem) - .map((child) => [path.basename(child.label), child as FolderItem]) + .map((child) => [path.basename(child.label), child as FolderItem]), ); } } @@ -208,7 +208,7 @@ export class PyTaskProvider implements vscode.TreeDataProvider { // - from pytask import Product, task // - from pytask import (Product, task) const fromPytaskImport = content.match( - /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/ + /from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/, ); const importPytask = content.match(/import\s+pytask(?:\s+as\s+(\w+))?\s*$/m); diff --git a/src/test/providers/taskProvider.test.ts b/src/test/providers/taskProvider.test.ts index 69b97aa..61a2cbb 100644 --- a/src/test/providers/taskProvider.test.ts +++ b/src/test/providers/taskProvider.test.ts @@ -109,7 +109,7 @@ def not_a_task(): expect(emptyModule).to.exist; expect(emptyModule!.contextValue).to.equal( "module", - "Empty file should be shown as module" + "Empty file should be shown as module", ); // Verify empty module has no tasks diff --git a/src/test/runTest.ts b/src/test/runTest.ts index 4db6b4e..ee25e65 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -8,7 +8,7 @@ async function main() { // Create a temporary test workspace const testWorkspacePath = path.join( os.tmpdir(), - `pytask-test-${Math.random().toString(36).substring(2)}` + `pytask-test-${Math.random().toString(36).substring(2)}`, ); fs.mkdirSync(testWorkspacePath, { recursive: true }); console.log(`Test workspace created at: ${testWorkspacePath}`); From cd1682e6a597fab69f244a507d8927e9fff5a3c8 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 4 Jan 2025 01:11:09 +0100 Subject: [PATCH 8/9] Fix. --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4768e0..3ce0460 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,6 +21,7 @@ repos: types: [file] additional_dependencies: - eslint + - eslint/js - typescript - '@typescript-eslint/parser' - '@typescript-eslint/eslint-plugin' From 3b1f8e7968ddabb2647e605a233a8ef7a2424d32 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 4 Jan 2025 01:18:01 +0100 Subject: [PATCH 9/9] Fix. --- .pre-commit-config.yaml | 9 ++++----- .prettierrc | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3ce0460..7b19680 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,10 @@ repos: files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx types: [file] additional_dependencies: - - eslint - - eslint/js - - typescript - - '@typescript-eslint/parser' - - '@typescript-eslint/eslint-plugin' + - eslint@8.56.0 + - '@typescript-eslint/parser@6.18.1' + - '@typescript-eslint/eslint-plugin@6.18.1' + - '@eslint/js@8.56.0' - repo: https://github.com/lyz-code/yamlfix rev: 1.17.0 hooks: diff --git a/.prettierrc b/.prettierrc index de753c5..7733a7e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "printWidth": 100 + "printWidth": 100, + "singleQuote": false }