Skip to content

Guide Plugin Development

GitHub Actions edited this page Jan 25, 2026 · 2 revisions

πŸ”Œ VelinScript Plugin-Entwicklung

Eine vollstΓ€ndige Anleitung zum Erstellen von Plugins und Tools fΓΌr VelinScript 2.0.


πŸ“‹ Inhaltsverzeichnis

  1. Überblick
  2. Plugin-Typen
  3. Rust Tool Plugin
  4. VS Code Extensiona
  5. LSP Extension
  6. Best Practices
  7. Beispiele

🎯 Überblick

VelinScript unterstΓΌtzt verschiedene Arten von Plugins:

  • Rust Tool Plugins: CLI-Tools, die als separate Binaries kompiliert werden
  • VS Code Extensions: Editor-Integration fΓΌr VS Code
  • LSP Extensions: Language Server Protocol Erweiterungen

Alle Plugins kΓΆnnen auf die VelinScript Compiler-API zugreifen, um Code zu parsen, zu analysieren und zu transformieren.


πŸ”§ Plugin-Typen

1. Rust Tool Plugin

Ein eigenstΓ€ndiges CLI-Tool, das als Binary kompiliert wird. Beispiele:

  • velin-lint - Code-QualitΓ€tsprΓΌfung
  • velin-security - Security-Scanner
  • velin-dead-code - Dead Code Detector

Vorteile:

  • βœ… Hohe Performance (native Rust)
  • βœ… Direkter Zugriff auf Compiler-API
  • βœ… Einfache Distribution als Binary
  • βœ… Kann in CI/CD integriert werden

2. VS Code Extension

Eine TypeScript-basierte Extension fΓΌr VS Code. Beispiele:

  • Syntax Highlighting
  • Code Completion
  • Debugger Integration

Vorteile:

  • βœ… Direkte Editor-Integration
  • βœ… Benutzerfreundliche UI
  • βœ… Schnelle Entwicklung

3. LSP Extension

Erweitert den Language Server Protocol Server. Beispiele:

  • Custom Code Actions
  • Erweiterte Hover-Informationen
  • Custom Diagnostics

Vorteile:

  • βœ… Funktioniert mit allen LSP-kompatiblen Editoren
  • βœ… Zentralisierte Logik
  • βœ… Konsistente Erfahrung

πŸ¦€ Rust Tool Plugin

Schritt 1: Projekt-Struktur erstellen

# Im tools/ Verzeichnis
cd tools
mkdir my-plugin
cd my-plugin

Schritt 2: Cargo.toml erstellen

[package]
name = "velin-my-plugin"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "velin-my-plugin"
path = "src/main.rs"

[dependencies]
clap = { version = "4.0", features = ["derive"] }
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
velin-compiler = { path = "../../compiler" }
walkdir = "2.0"  # Optional, fΓΌr Verzeichnis-Traversierung

Schritt 3: Grundstruktur (src/main.rs)

// VelinScript My Plugin
// Beschreibung deines Plugins

mod analyzer;  // Deine Plugin-Logik

use clap::{Parser, Subcommand};
use anyhow::Result;
use std::path::PathBuf;
use velin_compiler::parser::parser::Parser as VelinParser;

#[derive(Parser)]
#[command(name = "velin-my-plugin")]
#[command(about = "VelinScript My Plugin - Beschreibung", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Hauptbefehl deines Plugins
    Analyze {
        /// Eingabe-Datei oder Verzeichnis
        #[arg(short, long, default_value = ".")]
        input: PathBuf,
        
        /// Optionale Flag
        #[arg(short, long)]
        verbose: bool,
        
        /// JSON-Output
        #[arg(short, long)]
        json: bool,
    },
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Analyze { input, verbose, json } => {
            analyze_command(input, verbose, json)
        }
    }
}

fn analyze_command(input: PathBuf, verbose: bool, json: bool) -> Result<()> {
    println!("πŸ” Analysiere: {}\n", input.display());
    
    // Lade VelinScript-Dateien
    let files = collect_velin_files(&input)?;
    
    if files.is_empty() {
        eprintln!("Keine VelinScript-Dateien gefunden");
        return Ok(());
    }
    
    let mut results = Vec::new();
    
    // Analysiere jede Datei
    for file in &files {
        if verbose {
            println!("πŸ“ Analysiere: {}", file.display());
        }
        
        let content = std::fs::read_to_string(file)?;
        
        // Parse VelinScript Code
        let program = VelinParser::parse(&content)
            .map_err(|e| anyhow::anyhow!("Parse error: {}", e.message))?;
        
        // FΓΌhre deine Analyse durch
        let analysis_result = analyzer::analyze(&program, file)?;
        results.push(analysis_result);
    }
    
    // Output generieren
    if json {
        let json_output = serde_json::json!({
            "results": results,
            "total": results.len()
        });
        println!("{}", serde_json::to_string_pretty(&json_output)?);
    } else {
        // Text-Output
        for result in &results {
            println!("{}: {}", result.file.display(), result.message);
        }
    }
    
    Ok(())
}

fn collect_velin_files(path: &PathBuf) -> Result<Vec<PathBuf>> {
    let mut files = Vec::new();
    
    if path.is_file() {
        if path.extension().and_then(|s| s.to_str()) == Some("velin") {
            files.push(path.clone());
        }
    } else if path.is_dir() {
        for entry in walkdir::WalkDir::new(path) {
            let entry = entry?;
            if entry.file_type().is_file() {
                if entry.path().extension().and_then(|s| s.to_str()) == Some("velin") {
                    files.push(entry.path().to_path_buf());
                }
            }
        }
    }
    
    Ok(files)
}

Schritt 4: Plugin-Logik (src/analyzer.rs)

use anyhow::Result;
use std::path::PathBuf;
use velin_compiler::parser::ast::Program;

pub struct AnalysisResult {
    pub file: PathBuf,
    pub message: String,
    pub severity: Severity,
}

pub enum Severity {
    Info,
    Warning,
    Error,
}

pub fn analyze(program: &Program, file: &PathBuf) -> Result<AnalysisResult> {
    // Deine Analyse-Logik hier
    // Beispiel: ZΓ€hle Funktionen
    
    let function_count = program.items.iter()
        .filter(|item| matches!(item, velin_compiler::parser::ast::Item::Function(_)))
        .count();
    
    Ok(AnalysisResult {
        file: file.clone(),
        message: format!("Gefunden: {} Funktionen", function_count),
        severity: Severity::Info,
    })
}

Schritt 5: Kompilieren und Testen

# Kompilieren
cargo build --release

# Binary ist jetzt verfΓΌgbar unter:
# target/release/velin-my-plugin

# Testen
./target/release/velin-my-plugin analyze -i examples/

Schritt 6: Integration in VelinScript

Falls du mΓΆchtest, dass dein Plugin in der Haupt-CLI verfΓΌgbar ist, kannst du es als Subcommand hinzufΓΌgen:

// In compiler/src/cli.rs
#[derive(Subcommand)]
pub enum Commands {
    // ... bestehende Commands ...
    
    /// Mein Custom Plugin
    MyPlugin {
        #[command(subcommand)]
        subcommand: MyPluginCommands,
    },
}

🎨 VS Code Extension

Schritt 1: Projekt-Struktur erstellen

cd tools
mkdir my-vscode-extension
cd my-vscode-extension
npm init -y

Schritt 2: package.json konfigurieren

{
  "name": "velin-my-extension",
  "displayName": "VelinScript My Extension",
  "description": "Meine Custom VelinScript Extension",
  "version": "0.1.0",
  "publisher": "velinscript",
  "engines": {
    "vscode": "^1.60.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "onLanguage:velin"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "velin.myCommand",
        "title": "My Custom Command"
      }
    ]
  },
  "scripts": {
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./"
  },
  "dependencies": {
    "vscode": "^1.60.0"
  },
  "devDependencies": {
    "@types/node": "^18.0.0",
    "@types/vscode": "^1.60.0",
    "typescript": "^5.0.0"
  }
}

Schritt 3: TypeScript Extension (src/extension.ts)

import * as vscode from 'vscode';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

export function activate(context: vscode.ExtensionContext) {
    console.log('VelinScript My Extension ist jetzt aktiv!');
    
    // Command registrieren
    const disposable = vscode.commands.registerCommand(
        'velin.myCommand',
        async () => {
            const editor = vscode.window.activeTextEditor;
            
            if (!editor || editor.document.languageId !== 'velin') {
                vscode.window.showWarningMessage(
                    'Bitte ΓΆffne eine .velin Datei'
                );
                return;
            }
            
            const document = editor.document;
            const filePath = document.fileName;
            
            // FΓΌhre dein Tool aus
            try {
                const { stdout, stderr } = await execAsync(
                    `velin-my-plugin analyze -i "${filePath}"`
                );
                
                // Zeige Ergebnis
                const outputChannel = vscode.window.createOutputChannel(
                    'VelinScript My Plugin'
                );
                outputChannel.appendLine(stdout);
                outputChannel.show();
                
                vscode.window.showInformationMessage(
                    'Analyse erfolgreich!'
                );
            } catch (error: any) {
                vscode.window.showErrorMessage(
                    `Fehler: ${error.message}`
                );
            }
        }
    );
    
    context.subscriptions.push(disposable);
}

export function deactivate() {
    console.log('VelinScript My Extension wurde deaktiviert');
}

Schritt 4: TypeScript konfigurieren (tsconfig.json)

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES2020",
    "outDir": "out",
    "lib": ["ES2020"],
    "sourceMap": true,
    "rootDir": "src",
    "strict": true
  },
  "exclude": ["node_modules", ".vscode-test"]
}

Schritt 5: Kompilieren und Testen

# Dependencies installieren
npm install

# Kompilieren
npm run compile

# In VS Code testen
# 1. F5 drΓΌcken (Start Debugging)
# 2. Extension Development Host ΓΆffnet sich
# 3. Command Palette (Ctrl+Shift+P)
# 4. "My Custom Command" ausfΓΌhren

Schritt 6: Package erstellen

# VSIX Package erstellen
npm install -g vsce
vsce package

πŸ”Œ LSP Extension

Schritt 1: LSP Handler erstellen

// In tools/lsp/src/handlers/

use lsp_types::{
    CodeAction, CodeActionKind, CodeActionParams, Command,
    Diagnostic, DiagnosticSeverity, Position, Range,
};
use velin_compiler::parser::parser::Parser;

pub fn handle_custom_code_action(
    params: CodeActionParams,
    document: &str,
) -> Vec<CodeAction> {
    let mut actions = Vec::new();
    
    // Parse VelinScript Code
    if let Ok(program) = Parser::parse(document) {
        // Analysiere Code und erstelle Code Actions
        // Beispiel: Quick Fix fΓΌr hΓ€ufige Fehler
        
        let action = CodeAction {
            title: "Fix: Add missing return type".to_string(),
            kind: Some(CodeActionKind::QUICKFIX),
            diagnostics: None,
            edit: None,
            command: Some(Command {
                title: "Fix".to_string(),
                command: "velin.fix".to_string(),
                arguments: None,
            }),
            is_preferred: Some(true),
            disabled: None,
            data: None,
        };
        
        actions.push(action);
    }
    
    actions
}

pub fn handle_custom_diagnostics(document: &str) -> Vec<Diagnostic> {
    let mut diagnostics = Vec::new();
    
    // Parse und analysiere Code
    if let Ok(program) = Parser::parse(document) {
        // Erstelle Custom Diagnostics
        // Beispiel: Warnung bei zu langen Funktionen
        
        for item in &program.items {
            if let velin_compiler::parser::ast::Item::Function(func) = item {
                // PrΓΌfe Funktion-LΓ€nge
                // ... deine Logik ...
                
                let diagnostic = Diagnostic {
                    range: Range {
                        start: Position { line: 0, character: 0 },
                        end: Position { line: 0, character: 0 },
                    },
                    severity: Some(DiagnosticSeverity::WARNING),
                    code: Some(lsp_types::NumberOrString::String(
                        "custom-warning".to_string()
                    )),
                    source: Some("velin-my-plugin".to_string()),
                    message: "Funktion ist zu lang".to_string(),
                    related_information: None,
                    tags: None,
                    data: None,
                };
                
                diagnostics.push(diagnostic);
            }
        }
    }
    
    diagnostics
}

Schritt 2: In LSP Server integrieren

// In tools/lsp/src/server.rs

use handlers::custom::{handle_custom_code_action, handle_custom_diagnostics};

// In der Code Action Handler-Funktion
fn handle_code_action(params: CodeActionParams) -> Result<Vec<CodeAction>> {
    let document = get_document(&params.text_document.uri)?;
    
    // Standard LSP Actions
    let mut actions = standard_code_actions(&params, &document)?;
    
    // Custom Actions hinzufΓΌgen
    let custom_actions = handle_custom_code_action(params, &document);
    actions.extend(custom_actions);
    
    Ok(actions)
}

// In der Diagnostics-Funktion
fn publish_diagnostics(uri: &Url) -> Result<Vec<Diagnostic>> {
    let document = get_document(uri)?;
    
    // Standard Diagnostics
    let mut diagnostics = standard_diagnostics(&document)?;
    
    // Custom Diagnostics hinzufΓΌgen
    let custom_diagnostics = handle_custom_diagnostics(&document);
    diagnostics.extend(custom_diagnostics);
    
    Ok(diagnostics)
}

βœ… Best Practices

1. Fehlerbehandlung

use anyhow::{Context, Result};

fn process_file(path: &PathBuf) -> Result<()> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("Failed to read: {}", path.display()))?;
    
    // ... Verarbeitung ...
    
    Ok(())
}

2. Logging

use tracing::{info, warn, error};

fn analyze(program: &Program) -> Result<()> {
    info!("Starting analysis");
    
    // ... Analyse ...
    
    if found_issues {
        warn!("Found {} issues", count);
    }
    
    Ok(())
}

3. Performance

// Verwende Parallelisierung für große Projekte
use rayon::prelude::*;

files.par_iter().for_each(|file| {
    // Parallele Verarbeitung
});

4. Konfiguration

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct PluginConfig {
    enabled: bool,
    max_issues: usize,
    rules: Vec<String>,
}

fn load_config() -> Result<PluginConfig> {
    let config_path = std::env::current_dir()?
        .join("velin-plugin.toml");
    
    if config_path.exists() {
        let content = std::fs::read_to_string(config_path)?;
        let config: PluginConfig = toml::from_str(&content)?;
        Ok(config)
    } else {
        Ok(PluginConfig::default())
    }
}

5. Testing

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_analyzer() {
        let code = r#"
            fn test(): string {
                return "hello";
            }
        "#;
        
        let program = Parser::parse(code).unwrap();
        let result = analyzer::analyze(&program, &PathBuf::from("test.velin")).unwrap();
        
        assert_eq!(result.message, "Gefunden: 1 Funktionen");
    }
}

πŸ“š Beispiele

Beispiel 1: Complexity Analyzer

// src/analyzer.rs

pub fn analyze_complexity(program: &Program) -> Result<Vec<ComplexityIssue>> {
    let mut issues = Vec::new();
    
    for item in &program.items {
        if let Item::Function(func) = item {
            let complexity = calculate_complexity(&func.body);
            
            if complexity > 10 {
                issues.push(ComplexityIssue {
                    function: func.name.clone(),
                    complexity,
                    suggestion: "Funktion in kleinere Funktionen aufteilen".to_string(),
                });
            }
        }
    }
    
    Ok(issues)
}

fn calculate_complexity(body: &Block) -> usize {
    // ZΓ€hle if, while, for, match Statements
    // ... Implementierung ...
    0
}

Beispiel 2: Dependency Analyzer

// src/analyzer.rs

pub fn analyze_dependencies(program: &Program) -> Result<DependencyGraph> {
    let mut graph = DependencyGraph::new();
    
    for item in &program.items {
        if let Item::Function(func) = item {
            let dependencies = extract_function_calls(&func.body);
            graph.add_node(&func.name, dependencies);
        }
    }
    
    Ok(graph)
}

Beispiel 3: Code Metrics Plugin

// src/main.rs

#[derive(Subcommand)]
enum Commands {
    /// Zeigt Code-Metriken
    Metrics {
        #[arg(short, long)]
        input: PathBuf,
        
        #[arg(short, long)]
        format: Option<String>,
    },
}

fn metrics_command(input: PathBuf, format: Option<String>) -> Result<()> {
    let files = collect_velin_files(&input)?;
    let mut metrics = CodeMetrics::new();
    
    for file in &files {
        let content = std::fs::read_to_string(file)?;
        let program = Parser::parse(&content)?;
        
        metrics.analyze(&program);
    }
    
    match format.as_deref() {
        Some("json") => {
            println!("{}", serde_json::to_string_pretty(&metrics)?);
        }
        _ => {
            println!("πŸ“Š Code-Metriken:");
            println!("  Funktionen: {}", metrics.function_count);
            println!("  Structs: {}", metrics.struct_count);
            println!("  Enums: {}", metrics.enum_count);
            println!("  Durchschnittliche Funktion-LΓ€nge: {}", metrics.avg_function_length);
        }
    }
    
    Ok(())
}

πŸš€ VerΓΆffentlichung

Rust Tool Plugin

  1. Repository erstellen

    git init
    git add .
    git commit -m "Initial commit"
  2. Cargo.toml Metadata hinzufΓΌgen

    [package]
    name = "velin-my-plugin"
    version = "0.1.0"
    authors = ["Dein Name <email@example.com>"]
    license = "MIT"
    repository = "https://github.com/username/velin-my-plugin"
    description = "Beschreibung deines Plugins"
  3. Auf crates.io verΓΆffentlichen (optional)

    cargo publish

VS Code Extension

  1. VSIX Package erstellen

    vsce package
  2. Auf Marketplace verΓΆffentlichen

    vsce publish

πŸ“– Weitere Ressourcen


πŸ’‘ Tipps

  1. Starte klein: Beginne mit einem einfachen Plugin und erweitere es schrittweise
  2. Nutze bestehende Tools: Schaue dir velin-lint oder velin-security als Referenz an
  3. Teste grΓΌndlich: Erstelle Tests fΓΌr deine Plugin-Logik
  4. Dokumentiere: Erstelle eine README mit Beispielen
  5. Community: Teile dein Plugin in der Community

Viel Erfolg beim Plugin-Entwickeln! πŸš€


← ZurΓΌck zur Übersicht

Clone this wiki locally