models/fileInfo.psm1
|
using module ..\models\bundlerConfig.psm1 using namespace System.Management.Automation.Language Class FileInfo { # App config [BundlerConfig]$_config # file unique id [string]$id # file full path [string]$path # File consumers inf @{[FileInfo]File, [Ast]PathAst, [Ast]ImportAst, [string]Type}o [hashtable]$consumers = @{} # File imports info @{[FileInfo]File, [Ast]PathAst, [Ast]ImportAst, [string]Type} [hashtable]$imports = @{} # Is file entry [bool]$isEntry = $false # File Ast [Ast]$ast = $null # File tokens [System.Collections.ObjectModel.ReadOnlyCollection[System.Management.Automation.Language.Token]]$tokens = $null # Is file contains only types (classes, interfaces, structs, enums) [bool]$typesOnly FileInfo ([string]$filePath, [BundlerConfig]$config, [bool]$isEntry = $false, [hashtable]$consumerInfo = $null) { $this._config = $config $this.id = [Guid]::NewGuid().ToString("N") $this.path = $filePath $this.isEntry = $isEntry $fileContent = $this.GetFileContent($filePath, $consumerInfo) $this.ast = $fileContent.ast $this.tokens = $fileContent.tokens $this.typesOnly = $this.IsFileContainsTypesOnly() $this.LinkToConsumer($consumerInfo) } [hashtable]GetFileContent([string]$filePath, [hashtable]$consumerInfo = $null) { try { if (-not (Test-Path $filePath)) { $consumerStr = "" if ($consumerInfo) { $consumerStr = "imported by $($consumerInfo.file.path)" } Throw "File not found: $filePath $consumerStr" } $source = Get-Content $filePath -Raw if ($this._config.stripComments) { $source = $this.stripComments($source) } $errors = $null $tokensVal = $null $astVal = [System.Management.Automation.Language.Parser]::ParseInput($source, [ref]$tokensVal, [ref]$errors) #$realErrors = $errors | Where-Object { $_.ErrorId -notin @('TypeNotFound', 'VariableNotFound', 'CommandNotFound') } $realErrors = $errors | Where-Object { $_.ErrorId -notin @('TypeNotFound') } if ($realErrors.Count -gt 0) { Write-Host "Found syntax errors in script '$filePath':" -ForegroundColor Red foreach ($err in $realErrors) { $lineNum = $source.Substring(0, $err.Extent.StartOffset).Split("`n").Count Write-Host ("[{0}] {1}" -f $lineNum, $err.Message) -ForegroundColor Yellow } throw "Syntax errors in script '$filePath'" } return @{ tokens = $tokensVal ast = $astVal } } catch { throw "HANDLED: Error parsing file: $($_.Exception.Message)" } } [string]stripComments([string]$source) { $errors = $null $tokensVal = $null [System.Management.Automation.Language.Parser]::ParseInput($source, [ref]$tokensVal, [ref]$errors) $replacements = [System.Collections.ArrayList]::new() $tokenKind = [System.Management.Automation.Language.TokenKind] $fileBegin = $true for ($i = 0; $i -lt $tokensVal.Count; $i++) { $token = $tokensVal[$i] if ( $this._config.keepHeaderComments -and $this.isEntry -and $fileBegin -and ($token.Kind -eq $tokenKind::Comment -or $token.Kind -eq $tokenKind::NewLine)) { continue } $fileBegin = $false if ($token.Kind -ne $tokenKind::Comment) { continue } $replacements.Add(@{start = $token.Extent.StartOffset; Length = $token.Extent.EndOffset - $token.Extent.StartOffset; value = "" }) if (($i - 1) -gt 0 -and $tokensVal[$i - 1].Kind -eq $tokenKind::NewLine) { $replacements.Add(@{start = $tokensVal[$i - 1].Extent.StartOffset; Length = $tokensVal[$i - 1].Extent.EndOffset - $tokensVal[$i - 1].Extent.StartOffset; value = "" }) } } # WORKAROUND: System.Collections.ArrayList may unfold hashtables when sorting. So we have to convert it to array $sorted = ([hashtable[]]$replacements) | Sort-Object { $_['Start'] } -Descending $sb = [System.Text.StringBuilder]::new($source) foreach ($r in $sorted) { $sb.Remove($r.Start, $r.Length) } return $sb.ToString() } [void]LinkToConsumer([hashtable]$consumerInfo) { if (-not $consumerInfo) { return } $this.consumers[$consumerInfo.file.path] = $consumerInfo $consumerInfo.file.imports[$this.path] = @{ File = $this PathAst = $consumerInfo.pathAst ImportAst = $consumerInfo.importAst Type = $consumerInfo.type } } [bool]IsFileContainsTypesOnly() { $types = $this.Ast.FindAll( { $args[0] -is [TypeDefinitionAst] }, $false) if (-not $types) { return $false } $varsAndFunctions = $this.Ast.FindAll( { param($node) #WORKAROUND: FindAll with nested parameter $false ignores nested scriptblocks only, and finds all nodes within class # So we need manually check if node is inside class $p = $node.Parent while ($null -ne $p) { if ($p -is [TypeDefinitionAst]) { return $false } $p = $p.Parent } return $node -is [AssignmentStatementAst] -or $node -is [FunctionDefinitionAst] }, $false) if ($varsAndFunctions) { return $false } return $true } } |