Modules/businessdev.ALbuild.Apps/Public/Resolve-BcAnalyzerConfig.ps1
|
function Resolve-BcAnalyzerConfig { <# .SYNOPSIS Determines the code analyzers and ruleset for a compile, layering the sources by precedence. .DESCRIPTION Precedence (highest first): an explicit value (the task input) > the project's albuild.json (Analyzers / RuleSet) > the project's .vscode/settings.json ('al.codeAnalyzers' / 'al.ruleSetPath'). Only the highest source that supplies a value is used (the sources do not merge), matching how an explicit pipeline input overrides committed config. Analyzer entries are returned as given (tokens / short names / DLL paths - Resolve-BcAnalyzer maps them at compile time); a relative ruleset path is resolved against the project folder. 'al.enableCodeAnalysis': false in the project's .vscode/settings.json turns the analyzers OFF for that project, whatever any config source lists - it is how a team says 'do not analyse this one' in the editor, and a build that says otherwise would contradict what they see while writing the code. Only an explicit input overrides it; the ruleset is left as-is (it still governs the compiler's own diagnostics). AnalysisDisabled reports it, so a caller that DEMANDS analyzers can tell 'deliberately off' from 'nobody configured any'. .PARAMETER ProjectFolder The AL project folder (holds app.json, optionally .vscode/settings.json and albuild.json). .PARAMETER WorkspaceRoot Workspace root for albuild.json resolution (merged root + app). Default: the project folder. .PARAMETER InputAnalyzers Explicit analyzers from the task input (comma/semicolon separated). Wins over config. .PARAMETER InputRuleSet Explicit ruleset path from the task input. Wins over config. .OUTPUTS PSCustomObject: Analyzers (string[]), RuleSet (string), AnalyzersSource, RuleSetSource, AnalysisDisabled (bool). #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [string] $ProjectFolder, [string] $WorkspaceRoot, [string] $InputAnalyzers, [string] $InputRuleSet ) if (-not $WorkspaceRoot) { $WorkspaceRoot = $ProjectFolder } # albuild.json (Analyzers / RuleSet) $cfgAnalyzers = @(); $cfgRuleSet = '' $albuildJsonPath = Join-Path $ProjectFolder 'albuild.json' # Checked up front, not in the catch: Get-ALbuildProjectConfig swallows a malformed file and simply # returns nothing, which is the same silent "no configuration" that hid Banking's missing analyzers. if (Test-Path -LiteralPath $albuildJsonPath) { try { $null = ConvertFrom-BcJsonc -Text (Get-Content -LiteralPath $albuildJsonPath -Raw) } catch { throw ("'$albuildJsonPath' could not be parsed as JSON: $($_.Exception.Message)$([Environment]::NewLine)" + 'Analyzer configuration could not be read; refusing to compile without analyzers. Fix the ' + 'file, or pass the analyzers explicitly on the task input.') } } try { $cfg = Get-ALbuildProjectConfig -AppFolder $ProjectFolder -WorkspaceRoot $WorkspaceRoot if ($cfg) { if ($cfg.Analyzers) { $cfgAnalyzers = @($cfg.Analyzers) } if ($cfg.RuleSet) { $cfgRuleSet = [string] $cfg.RuleSet } } } catch { # The file parses (checked above); Get-ALbuildProjectConfig throwing here means something else - # a missing file, a schema it dislikes - and that is not this function's business. Write-Verbose "No albuild.json analyzer config at '$ProjectFolder': $($_.Exception.Message)" } # .vscode/settings.json (al.codeAnalyzers / al.ruleSetPath / al.enableCodeAnalysis) $setAnalyzers = @(); $setRuleSet = ''; $analysisDisabled = $false $settingsPath = Join-Path $ProjectFolder '.vscode/settings.json' if (Test-Path -LiteralPath $settingsPath) { $raw = Get-Content -LiteralPath $settingsPath -Raw $settings = $null try { $settings = ConvertFrom-BcJsonc -Text $raw } catch { # The file belongs to VS Code and its authors keep writing JSONC in it, so the reader is # tolerant of comments and trailing commas. Whatever is STILL unparsable stops the build # rather than silently yielding no analyzers. throw ("'$settingsPath' could not be parsed as JSON: $($_.Exception.Message)$([Environment]::NewLine)" + 'Analyzer configuration could not be read; refusing to compile without analyzers. Fix the ' + 'file, or pass the analyzers explicitly on the task input.') } # Asked for, not assumed: under Set-StrictMode reading a property that is not there throws, and # a settings.json with analyzers but no ruleset is perfectly normal. This used to sit inside the # try/catch that swallowed everything - which is precisely how the real parse failure hid. $props = $settings.PSObject.Properties if ($props['al.codeAnalyzers'] -and $props['al.codeAnalyzers'].Value) { $setAnalyzers = @($props['al.codeAnalyzers'].Value) } if ($props['al.ruleSetPath'] -and $props['al.ruleSetPath'].Value) { $setRuleSet = [string] $props['al.ruleSetPath'].Value } # Only an explicit 'false' counts, and the asymmetry is deliberate. 'al.enableCodeAnalysis' is # how the EDITOR is switched on (Microsoft's own instructions say to set it to true, and the # extension ships no default), but 'al.codeAnalyzers' is what SELECTS the analyzers - and a # project that lists three of them has said what it wants analysed. Reading an absent switch as # 'off' would strip the gate from 21 projects that list analyzers without it, 15 of them # production apps the pipeline analyses today. Retracting the list has to be said, not omitted. if ($props['al.enableCodeAnalysis'] -and $props['al.enableCodeAnalysis'].Value -is [bool]) { $analysisDisabled = -not [bool] $props['al.enableCodeAnalysis'].Value } } # Precedence: task input > albuild.json > settings.json. $analyzers = @(); $aSource = 'none' if (-not [string]::IsNullOrWhiteSpace($InputAnalyzers)) { $analyzers = @($InputAnalyzers -split '[;,]' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) $aSource = 'task input' } elseif ($cfgAnalyzers.Count -gt 0) { $analyzers = $cfgAnalyzers; $aSource = 'albuild.json' } elseif ($setAnalyzers.Count -gt 0) { $analyzers = $setAnalyzers; $aSource = '.vscode/settings.json' } $rawRule = ''; $rSource = 'none' if (-not [string]::IsNullOrWhiteSpace($InputRuleSet)) { $rawRule = $InputRuleSet; $rSource = 'task input' } elseif ($cfgRuleSet) { $rawRule = $cfgRuleSet; $rSource = 'albuild.json' } elseif ($setRuleSet) { $rawRule = $setRuleSet; $rSource = '.vscode/settings.json' } $ruleset = '' if ($rawRule) { $ruleset = if ([System.IO.Path]::IsPathRooted($rawRule)) { $rawRule } else { Join-Path $ProjectFolder $rawRule } } # 'al.enableCodeAnalysis': false is the project's own statement that it is not analysed - the way # the projects that opt out (Banking's test and demo, ERiC's test, ...) say it. It wins over both # config sources, because a build that analysed what the editor deliberately leaves alone would # report findings nobody sees while writing the code. An explicit input still overrides it: a # caller asking for analyzers by name is making the decision here and now. # # There used to be a blanket 'a test app gets no analyzers' rule in this spot. It was wrong: test # projects that DO configure analyzers pair them with their own testApp.ruleset.json (AS0084 off # for the 50000..99999 ID range, and so on), and the rule threw that away. $optedOut = $analysisDisabled -and $aSource -ne 'task input' if ($optedOut) { if ($analyzers.Count -gt 0) { Write-ALbuildLog "'$ProjectFolder' sets al.enableCodeAnalysis = false: no analyzers (was $($analyzers -join ', ') from $aSource)." } $analyzers = @() $aSource = '.vscode/settings.json (al.enableCodeAnalysis = false)' } return [PSCustomObject]@{ Analyzers = @($analyzers) RuleSet = $ruleset AnalyzersSource = $aSource RuleSetSource = $rSource AnalysisDisabled = $optedOut } } |