microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
79db525325e5e5bc087af12d0fc658c8db2d9458

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/linting/Invoke-PythonLint.ps1

201lines · modecode

1#!/usr/bin/env pwsh
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4#
5# Invoke-PythonLint.ps1
6#
7# Purpose: Python lint runner. Discovers Python skills via pyproject.toml and
8# invokes ruff against each. Defaults to read-only `ruff check` for CI
9# gating. With `-Fix`, applies `ruff check --fix` followed by
10# `ruff format` (mutates source; intended for local developer use).
11# Author: HVE Core Team
12
13#Requires -Version 7.0
14
15[CmdletBinding()]
16param(
17 [Parameter(Mandatory = $false)]
18 [string]$RepoRoot = (Get-Location).Path,
19
20 [Parameter(Mandatory = $false)]
21 [string]$OutputPath,
22
23 [Parameter(Mandatory = $false)]
24 [switch]$Fix
25)
26
27$ErrorActionPreference = 'Stop'
28
29Import-Module (Join-Path $PSScriptRoot 'Modules/PythonLintHelpers.psm1') -Force
30
31#region Functions
32
33function Invoke-PythonLint {
34 [CmdletBinding()]
35 [OutputType([hashtable])]
36 param(
37 [Parameter(Mandatory = $true)]
38 [string]$RepoRoot,
39
40 [Parameter(Mandatory = $false)]
41 [string]$OutputPath,
42
43 [Parameter(Mandatory = $false)]
44 [switch]$Fix
45 )
46
47 Push-Location $RepoRoot
48 try {
49 $pythonSkills = Get-PythonSkill -RepoRoot $RepoRoot
50
51 if (-not $pythonSkills) {
52 Write-Host 'No Python skills found (no pyproject.toml files detected)' -ForegroundColor Yellow
53 return @{ success = $true; skillsChecked = 0; errors = @() }
54 }
55
56 Write-Host "Found $($pythonSkills.Count) Python skill(s):" -ForegroundColor Cyan
57 $pythonSkills | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray }
58
59 $globalRuffAvailable = [bool](Get-Command ruff -ErrorAction SilentlyContinue)
60
61 $results = @{
62 success = $true
63 skillsChecked = 0
64 errors = @()
65 details = @()
66 }
67
68 foreach ($skillPath in $pythonSkills) {
69 if ($Fix) {
70 Write-Host "`nRunning ruff --fix and ruff format in $skillPath..." -ForegroundColor Cyan
71 } else {
72 Write-Host "`nRunning ruff in $skillPath..." -ForegroundColor Cyan
73 }
74
75 Push-Location $skillPath
76 try {
77 $ruffCmd = Resolve-RuffCommand -SkillPath $skillPath -GlobalRuffAvailable $globalRuffAvailable
78
79 if (-not $ruffCmd) {
80 Write-Host '❌ ruff not available (no .venv and not installed globally)' -ForegroundColor Red
81 $results.success = $false
82 $results.errors += $skillPath
83 continue
84 }
85
86 if ($Fix) {
87 # Step 1: autofix lint rules
88 $fixOutput = & $ruffCmd check . --fix 2>&1
89 $fixExit = $LASTEXITCODE
90
91 # Step 2: apply formatter (issue #886 acceptance criterion)
92 $formatOutput = & $ruffCmd format . 2>&1
93 $formatExit = $LASTEXITCODE
94
95 $combinedOutput = (@($fixOutput) + @($formatOutput)) | Out-String
96 $passed = ($fixExit -eq 0 -and $formatExit -eq 0)
97
98 $result = @{
99 path = $skillPath
100 passed = $passed
101 output = $combinedOutput
102 fixExitCode = $fixExit
103 formatExitCode = $formatExit
104 }
105
106 $results.details += $result
107 $results.skillsChecked++
108
109 if (-not $passed) {
110 Write-Host "$combinedOutput" -ForegroundColor Red
111 if ($fixExit -ne 0) {
112 Write-Host '❌ Unfixable linting issues remain' -ForegroundColor Red
113 }
114 if ($formatExit -ne 0) {
115 Write-Host '❌ ruff format failed' -ForegroundColor Red
116 }
117 $results.success = $false
118 $results.errors += $skillPath
119 } else {
120 if ($combinedOutput.Trim()) {
121 Write-Host "$combinedOutput"
122 }
123 Write-Host '✓ Autofix and format complete' -ForegroundColor Green
124 }
125 } else {
126 $output = & $ruffCmd check . 2>&1
127 $exitCode = $LASTEXITCODE
128
129 $result = @{
130 path = $skillPath
131 passed = ($exitCode -eq 0)
132 output = $output | Out-String
133 }
134
135 $results.details += $result
136 $results.skillsChecked++
137
138 if ($exitCode -ne 0) {
139 Write-Host "$output" -ForegroundColor Red
140 Write-Host '❌ Linting issues found' -ForegroundColor Red
141 $results.success = $false
142 $results.errors += $skillPath
143 } else {
144 if ($output) {
145 Write-Host "$output"
146 }
147 Write-Host '✓ No linting issues' -ForegroundColor Green
148 }
149 }
150 } catch {
151 Write-Host "Error running ruff: $_" -ForegroundColor Red
152 $results.success = $false
153 $results.errors += "$skillPath - error: $_"
154 } finally {
155 Pop-Location
156 }
157 }
158
159 $defaultFile = if ($Fix) { 'python-lint-fix-results.json' } else { 'python-lint-results.json' }
160 $resolvedPath = Write-PythonLintResults -Results $results -RepoRoot $RepoRoot -OutputPath $OutputPath -DefaultFileName $defaultFile
161 Write-Host "📊 Results written to: $resolvedPath" -ForegroundColor Cyan
162
163 return $results
164 } finally {
165 Pop-Location
166 }
167}
168
169#endregion
170
171#region Main Execution
172
173# Don't run main logic if dot-sourced for testing
174if ($MyInvocation.InvocationName -ne '.') {
175 try {
176 $result = Invoke-PythonLint -RepoRoot $RepoRoot -OutputPath $OutputPath -Fix:$Fix
177
178 if ($result.success) {
179 if ($Fix) {
180 Write-Host "`n✅ Python lint autofix completed successfully" -ForegroundColor Green
181 } else {
182 Write-Host "`n✅ All Python skills passed linting" -ForegroundColor Green
183 }
184 exit 0
185 } else {
186 if ($Fix) {
187 Write-Host "`n❌ Python lint autofix completed with unfixable errors" -ForegroundColor Red
188 } else {
189 Write-Host "`n❌ Linting completed with errors" -ForegroundColor Red
190 }
191 exit 1
192 }
193 }
194 catch {
195 Write-CIAnnotation -Level 'Error' -Message $_.Exception.Message
196 Write-Error -ErrorAction Continue "Invoke-PythonLint failed: $($_.Exception.Message)"
197 exit 1
198 }
199}
200
201#endregion