diff --git a/src/application/Parser/Executable/Script/Compiler/ScriptCompilerFactory.ts b/src/application/Parser/Executable/Script/Compiler/ScriptCompilerFactory.ts index 1ed34dcf..a59b2d2f 100644 --- a/src/application/Parser/Executable/Script/Compiler/ScriptCompilerFactory.ts +++ b/src/application/Parser/Executable/Script/Compiler/ScriptCompilerFactory.ts @@ -80,7 +80,7 @@ class FunctionCallScriptCompiler implements ScriptCompiler { } const calls = parseFunctionCalls(script.call); const compiledCode = this.utilities.callCompiler.compileFunctionCalls(calls, this.functions); - validateCompiledCode( + validateFinalCompiledCode( compiledCode, this.language, this.utilities.codeValidator, @@ -95,7 +95,7 @@ class FunctionCallScriptCompiler implements ScriptCompiler { } } -function validateCompiledCode( +function validateFinalCompiledCode( compiledCode: CompiledCode, language: ScriptingLanguage, validate: CodeValidator, @@ -109,6 +109,7 @@ function validateCompiledCode( CodeValidationRule.NoEmptyLines, CodeValidationRule.NoTooLongLines, // Allow duplicated lines to enable calling same function multiple times + CodeValidationRule.NoCommentOnlyLines, ], ), ); diff --git a/src/application/Parser/Executable/Script/ScriptParser.ts b/src/application/Parser/Executable/Script/ScriptParser.ts index 05e6b8a8..010d83f4 100644 --- a/src/application/Parser/Executable/Script/ScriptParser.ts +++ b/src/application/Parser/Executable/Script/ScriptParser.ts @@ -95,6 +95,7 @@ function validateHardcodedCodeWithoutCalls( CodeValidationRule.NoEmptyLines, CodeValidationRule.NoDuplicatedLines, CodeValidationRule.NoTooLongLines, + CodeValidationRule.NoCommentOnlyLines, ], ), ); diff --git a/src/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeCommentOnlyCode.ts b/src/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeCommentOnlyCode.ts new file mode 100644 index 00000000..812b837a --- /dev/null +++ b/src/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeCommentOnlyCode.ts @@ -0,0 +1,33 @@ +import type { ScriptingLanguage } from '@/domain/ScriptingLanguage'; +import { isCommentLine, type CommentLineChecker } from './Common/CommentLineChecker'; +import { createSyntax, type SyntaxFactory } from './Syntax/SyntaxFactory'; +import type { CodeLine, CodeValidationAnalyzer, InvalidCodeLine } from './CodeValidationAnalyzer'; + +export type CommentOnlyCodeAnalyzer = CodeValidationAnalyzer & { + ( + ...args: [ + ...Parameters, + syntaxFactory?: SyntaxFactory, + commentLineChecker?: CommentLineChecker, + ] + ): ReturnType; +}; + +export const analyzeCommentOnlyCode: CommentOnlyCodeAnalyzer = ( + lines: readonly CodeLine[], + language: ScriptingLanguage, + syntaxFactory: SyntaxFactory = createSyntax, + commentLineChecker: CommentLineChecker = isCommentLine, +) => { + const syntax = syntaxFactory(language); + if (!lines.every((line) => commentLineChecker(line.text, syntax))) { + return []; + } + return lines + .map((line): InvalidCodeLine => ({ + lineNumber: line.lineNumber, + error: (() => { + return 'Script is consists of comments only'; + })(), + })); +}; diff --git a/src/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeDuplicateLines.ts b/src/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeDuplicateLines.ts index b41de254..0bb7378f 100644 --- a/src/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeDuplicateLines.ts +++ b/src/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeDuplicateLines.ts @@ -1,6 +1,7 @@ import type { LanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/LanguageSyntax'; import type { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { createSyntax, type SyntaxFactory } from './Syntax/SyntaxFactory'; +import { isCommentLine, type CommentLineChecker } from './Common/CommentLineChecker'; import type { CodeLine, CodeValidationAnalyzer, InvalidCodeLine } from './CodeValidationAnalyzer'; export type DuplicateLinesAnalyzer = CodeValidationAnalyzer & { @@ -8,6 +9,7 @@ export type DuplicateLinesAnalyzer = CodeValidationAnalyzer & { ...args: [ ...Parameters, syntaxFactory?: SyntaxFactory, + commentLineChecker?: CommentLineChecker, ] ): ReturnType; }; @@ -16,12 +18,13 @@ export const analyzeDuplicateLines: DuplicateLinesAnalyzer = ( lines: readonly CodeLine[], language: ScriptingLanguage, syntaxFactory: SyntaxFactory = createSyntax, + commentLineChecker: CommentLineChecker = isCommentLine, ) => { const syntax = syntaxFactory(language); return lines .map((line): CodeLineWithDuplicateOccurrences => ({ lineNumber: line.lineNumber, - shouldBeIgnoredInAnalysis: shouldIgnoreLine(line.text, syntax), + shouldBeIgnoredInAnalysis: shouldIgnoreLine(line.text, syntax, commentLineChecker), duplicateLineNumbers: lines .filter((other) => other.text === line.text) .map((duplicatedLine) => duplicatedLine.lineNumber), @@ -43,17 +46,15 @@ function isNonIgnorableDuplicateLine(line: CodeLineWithDuplicateOccurrences): bo return !line.shouldBeIgnoredInAnalysis && line.duplicateLineNumbers.length > 1; } -function shouldIgnoreLine(codeLine: string, syntax: LanguageSyntax): boolean { - return isCommentLine(codeLine, syntax) +function shouldIgnoreLine( + codeLine: string, + syntax: LanguageSyntax, + commentLineChecker: CommentLineChecker, +): boolean { + return commentLineChecker(codeLine, syntax) || isLineComposedEntirelyOfCommonCodeParts(codeLine, syntax); } -function isCommentLine(codeLine: string, syntax: LanguageSyntax): boolean { - return syntax.commentDelimiters.some( - (delimiter) => codeLine.startsWith(delimiter), - ); -} - function isLineComposedEntirelyOfCommonCodeParts( codeLine: string, syntax: LanguageSyntax, diff --git a/src/application/Parser/Executable/Script/Validation/Analyzers/Common/CommentLineChecker.ts b/src/application/Parser/Executable/Script/Validation/Analyzers/Common/CommentLineChecker.ts new file mode 100644 index 00000000..59e93966 --- /dev/null +++ b/src/application/Parser/Executable/Script/Validation/Analyzers/Common/CommentLineChecker.ts @@ -0,0 +1,14 @@ +import type { LanguageSyntax } from '../Syntax/LanguageSyntax'; + +export interface CommentLineChecker { + ( + codeLine: string, + syntax: LanguageSyntax, + ): boolean; +} + +export const isCommentLine: CommentLineChecker = (codeLine, syntax) => { + return syntax.commentDelimiters.some( + (delimiter) => codeLine.startsWith(delimiter), + ); +}; diff --git a/src/application/Parser/Executable/Script/Validation/CodeValidationRule.ts b/src/application/Parser/Executable/Script/Validation/CodeValidationRule.ts index a1206348..c5888e96 100644 --- a/src/application/Parser/Executable/Script/Validation/CodeValidationRule.ts +++ b/src/application/Parser/Executable/Script/Validation/CodeValidationRule.ts @@ -2,4 +2,5 @@ export enum CodeValidationRule { NoEmptyLines, NoDuplicatedLines, NoTooLongLines, + NoCommentOnlyLines, } diff --git a/src/application/Parser/Executable/Script/Validation/ValidationRuleAnalyzerFactory.ts b/src/application/Parser/Executable/Script/Validation/ValidationRuleAnalyzerFactory.ts index ac906f09..bd63f5fa 100644 --- a/src/application/Parser/Executable/Script/Validation/ValidationRuleAnalyzerFactory.ts +++ b/src/application/Parser/Executable/Script/Validation/ValidationRuleAnalyzerFactory.ts @@ -2,6 +2,7 @@ import { CodeValidationRule } from './CodeValidationRule'; import { analyzeDuplicateLines } from './Analyzers/AnalyzeDuplicateLines'; import { analyzeEmptyLines } from './Analyzers/AnalyzeEmptyLines'; import { analyzeTooLongLines } from './Analyzers/AnalyzeTooLongLines'; +import { analyzeCommentOnlyCode } from './Analyzers/AnalyzeCommentOnlyCode'; import type { CodeValidationAnalyzer } from './Analyzers/CodeValidationAnalyzer'; export interface ValidationRuleAnalyzerFactory { @@ -26,6 +27,8 @@ function createValidationRule(rule: CodeValidationRule): CodeValidationAnalyzer return analyzeDuplicateLines; case CodeValidationRule.NoTooLongLines: return analyzeTooLongLines; + case CodeValidationRule.NoCommentOnlyLines: + return analyzeCommentOnlyCode; default: throw new Error(`Unknown rule: ${rule}`); } diff --git a/src/application/collections/windows.yaml b/src/application/collections/windows.yaml index 63a51060..0fff37e9 100644 --- a/src/application/collections/windows.yaml +++ b/src/application/collections/windows.yaml @@ -40136,7 +40136,9 @@ functions: Remove the registry key "{{ $keyPath }}" {{ with $codeComment }}({{ . }}){{ end }} revertCodeComment: >- - Recreate the registry key "{{ $keyPath }}" + {{ with $recreateOnRevert }} + Recreate the registry key "{{ $keyPath }}" + {{ end }} - # Marked: refactor-with-variables # - Replacing SID is same as `CreateRegistryKey` @@ -40710,32 +40712,108 @@ functions: setupCodeUnelevated: '{{ with $setupCode }}{{ . }}{{ end }}' minimumWindowsVersion: '{{ with $minimumWindowsVersion }}{{ . }}{{ end }}' elevateToTrustedInstaller: '{{ with $elevateToTrustedInstaller }}true{{ end }}' + # Marked: refactor-with-variables + # - Setting registry value is same in `code` and `revert code` code: |- - $registryPath = '{{ $keyPath }}' + $name = '{{ $valueName }}' + $rawPath = '{{ $keyPath }}' + $rawType = '{{ $dataType }}' $data = '{{ $data }}' {{ with $evaluateDataAsPowerShell }} $data = $({{ $data }}) {{ end }} - reg add '{{ $keyPath }}' ` - /v '{{ $valueName }}' ` - /t '{{ $dataType }}' ` - /d "$data" ` - /f + Write-Host "Setting '$name' registry value in '$rawPath'." + $hive = $rawPath.Split('\')[0] + $path = $hive + ':' + $rawPath.Substring($hive.Length) + if (!(Test-Path $path)) { + New-Item -Path $path -Force -ErrorAction Stop | Out-Null + Write-Host 'Successfully created the parent registry key.' + } + $type = switch ($rawType) { + 'REG_SZ' { 'String' } + 'REG_DWORD' { 'DWord' } + 'REG_QWORD' { 'QWord' } + 'REG_EXPAND_SZ' { 'ExpandString' } + 'REG_MULTI_SZ' { 'MultiString' } + default { + throw "Internal privacy$([char]0x002E)sexy error: Failed to find data type for: '$rawType'." + } + } + $key = Get-Item -Path $path -ErrorAction Stop + $currentData = $key.GetValue($name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + if ($currentData -ne $null) { + $currentType = $key.GetValueKind($name) + if (($type -eq $currentType) -and ($currentData -eq $data)) { + Write-Output 'Skipping, no action needed.' + Exit 0 + } + } + Set-ItemProperty ` + -Path $path ` + -Name $name ` + -Value $data ` + -Type $type ` + -Force ` + -ErrorAction Stop + Write-Host 'Successfully configured.' revertCode: |- {{ with $deleteOnRevert }} - reg delete '{{ $keyPath }}' ` - /v '{{ $valueName }}' ` - /f 2>$null + $name = '{{ $valueName }}' + $rawPath = '{{ $keyPath }}' + Write-Host "Restoring '$name' registry value in '$rawPath' by deleting." + $hive = $rawPath.Split('\')[0] + $path = $hive + ':' + $rawPath.Substring($hive.Length) + if (!(Test-Path $path)) { + Write-Host 'Skipping: Parent key does not exist; no action needed.' + Exit 0 + } + Remove-ItemProperty ` + -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer' ` + -Name 'DisallowRun' ` + -Force ` + -ErrorAction Stop {{ end }}{{ with $dataOnRevert }} - $revertData = '{{ . }}' + $name = '{{ $valueName }}' + $rawPath = '{{ $keyPath }}' + $data = '{{ $data }}' + $rawType = '{{ $dataType }}' {{ with $evaluateDataAsPowerShell }} - $revertData = $({{ . }}) + $data = $({{ $data }}) {{ end }} - reg add '{{ $keyPath }}' ` - /v '{{ $valueName }}' ` - /t '{{ $dataType }}' ` - /d "$revertData" ` - /f + Write-Host "Restoring '$name' registry value in '$rawPath' by updating." + $hive = $rawPath.Split('\')[0] + $path = $hive + ':' + $rawPath.Substring($hive.Length) + if (!(Test-Path $path)) { + New-Item -Path $path -Force -ErrorAction Stop | Out-Null + Write-Host 'Successfully created the parent registry key.' + } + $type = switch ($rawType) { + 'REG_SZ' { 'String' } + 'REG_DWORD' { 'DWord' } + 'REG_QWORD' { 'QWord' } + 'REG_EXPAND_SZ' { 'ExpandString' } + 'REG_MULTI_SZ' { 'MultiString' } + default { + throw "Internal privacy$([char]0x002E)sexy error: Failed to find data type for: '$rawType'." + } + } + $key = Get-Item -Path $path -ErrorAction Stop + $currentData = $key.GetValue($name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + if ($currentData -ne $null) { + $currentType = $key.GetValueKind($name) + if (($type -eq $currentType) -and ($currentData -eq $data)) { + Write-Output 'Skipping, no action needed.' + Exit 0 + } + } + Set-ItemProperty ` + -Path $path ` + -Name $name ` + -Value $data ` + -Type $type ` + -Force ` + -ErrorAction Stop + Write-Host 'Successfully restored.' {{ end }} - name: EnableTLSProtocol