Fix PowerShell code block inlining in compiler
This commit enhances the compiler's ability to inline PowerShell code
blocks. Previously, the compiler attempted to inline all lines ending
with brackets (`}` and `{`) using semicolons, which leads to syntax
errors. This improvement allows for more flexible PowerShell code
writing with reliable outcomes.
Key Changes:
- Update InlinePowerShell pipe to handle code blocks specifically
- Extend unit tests for the InlinePowerShell pipe
Other supporting changes:
- Refactor InlinePowerShell tests for improved scalability
- Enhance pipe unit test running with regex support
- Expand test coverage for various PowerShell syntax used in
privacy.sexy
- Update related interfaces to align with new code conventions, dropping
`I` prefix
- Optimize line merging to skip lines already ending with semicolons
- Increase timeout in E2E tests to accommodate for slower application
load caused by more processing introduced in this commit.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
export interface IPipe {
|
||||
export interface Pipe {
|
||||
readonly name: string;
|
||||
apply(input: string): string;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { IPipe } from '../IPipe';
|
||||
import type { Pipe } from '../Pipe';
|
||||
|
||||
export class EscapeDoubleQuotes implements IPipe {
|
||||
export class EscapeDoubleQuotes implements Pipe {
|
||||
public readonly name: string = 'escapeDoubleQuotes';
|
||||
|
||||
public apply(raw: string): string {
|
||||
if (!raw) {
|
||||
return raw;
|
||||
return '';
|
||||
}
|
||||
return raw.replaceAll('"', '"^""');
|
||||
/* eslint-disable vue/max-len */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { splitTextIntoLines } from '@/application/Common/Text/SplitTextIntoLines';
|
||||
import type { IPipe } from '../IPipe';
|
||||
import type { Pipe } from '../Pipe';
|
||||
|
||||
export class InlinePowerShell implements IPipe {
|
||||
export class InlinePowerShell implements Pipe {
|
||||
public readonly name: string = 'inlinePowerShell';
|
||||
|
||||
public apply(code: string): string {
|
||||
@@ -9,9 +9,11 @@ export class InlinePowerShell implements IPipe {
|
||||
return code;
|
||||
}
|
||||
const processor = new Array<(data: string) => string>(...[ // for broken ESlint "indent"
|
||||
// Order is important
|
||||
inlineComments,
|
||||
mergeLinesWithBacktick,
|
||||
mergeHereStrings,
|
||||
mergeLinesWithBacktick,
|
||||
mergeLinesWithBracketCodeBlocks,
|
||||
mergeNewLines,
|
||||
]).reduce((a, b) => (data) => b(a(data)));
|
||||
const newCode = processor(code);
|
||||
@@ -105,12 +107,12 @@ function mergeHereStrings(code: string) {
|
||||
return quoted;
|
||||
});
|
||||
}
|
||||
interface IInlinedHereString {
|
||||
interface InlinedHereString {
|
||||
readonly quotesAround: string;
|
||||
readonly escapedQuotes: string;
|
||||
readonly separator: string;
|
||||
}
|
||||
function getHereStringHandler(quotes: string): IInlinedHereString {
|
||||
function getHereStringHandler(quotes: string): InlinedHereString {
|
||||
/*
|
||||
We handle @' and @" differently.
|
||||
Single quotes are interpreted literally and doubles are expandable.
|
||||
@@ -155,9 +157,33 @@ function mergeLinesWithBacktick(code: string) {
|
||||
return code.replaceAll(/ +`\s*(?:\r\n|\r|\n)\s*/g, ' ');
|
||||
}
|
||||
|
||||
function mergeNewLines(code: string) {
|
||||
return splitTextIntoLines(code)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
.join('; ');
|
||||
/**
|
||||
* Inlines code blocks in PowerShell scripts while preserving correct syntax.
|
||||
* It removes unnecessary newlines and spaces around brackets,
|
||||
* inlining the code where possible.
|
||||
* This prevents syntax errors like "Unexpected token '}'" when inlining brackets.
|
||||
*/
|
||||
function mergeLinesWithBracketCodeBlocks(code: string): string {
|
||||
return code
|
||||
// Opening bracket: [whitespace] Opening bracket (newline)
|
||||
.replace(/(?<=.*)\s*{[\r\n][\s\r\n]*/g, ' { ')
|
||||
// Closing bracket: [whitespace] Closing bracket (newline) (continuation keyword)
|
||||
.replace(/\s*}[\r\n][\s\r\n]*(?=elseif|else|catch|finally|until)/g, ' } ')
|
||||
.replace(/(?<=do\s*{.*)[\r\n\s]*}[\r\n][\r\n\s]*(?=while)/g, ' } '); // Do-While
|
||||
}
|
||||
|
||||
function mergeNewLines(code: string) {
|
||||
const nonEmptyLines = splitTextIntoLines(code)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0);
|
||||
|
||||
return nonEmptyLines
|
||||
.map((line, index) => {
|
||||
const isLastLine = index === nonEmptyLines.length - 1;
|
||||
if (isLastLine) {
|
||||
return line;
|
||||
}
|
||||
return line.endsWith(';') ? line : `${line};`;
|
||||
})
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { InlinePowerShell } from './PipeDefinitions/InlinePowerShell';
|
||||
import { EscapeDoubleQuotes } from './PipeDefinitions/EscapeDoubleQuotes';
|
||||
import type { IPipe } from './IPipe';
|
||||
import type { Pipe } from './Pipe';
|
||||
|
||||
const RegisteredPipes = [
|
||||
new EscapeDoubleQuotes(),
|
||||
@@ -8,19 +8,19 @@ const RegisteredPipes = [
|
||||
];
|
||||
|
||||
export interface IPipeFactory {
|
||||
get(pipeName: string): IPipe;
|
||||
get(pipeName: string): Pipe;
|
||||
}
|
||||
|
||||
export class PipeFactory implements IPipeFactory {
|
||||
private readonly pipes = new Map<string, IPipe>();
|
||||
private readonly pipes = new Map<string, Pipe>();
|
||||
|
||||
constructor(pipes: readonly IPipe[] = RegisteredPipes) {
|
||||
constructor(pipes: readonly Pipe[] = RegisteredPipes) {
|
||||
for (const pipe of pipes) {
|
||||
this.registerPipe(pipe);
|
||||
}
|
||||
}
|
||||
|
||||
public get(pipeName: string): IPipe {
|
||||
public get(pipeName: string): Pipe {
|
||||
validatePipeName(pipeName);
|
||||
const pipe = this.pipes.get(pipeName);
|
||||
if (!pipe) {
|
||||
@@ -29,7 +29,7 @@ export class PipeFactory implements IPipeFactory {
|
||||
return pipe;
|
||||
}
|
||||
|
||||
private registerPipe(pipe: IPipe): void {
|
||||
private registerPipe(pipe: Pipe): void {
|
||||
validatePipeName(pipe.name);
|
||||
if (this.pipes.has(pipe.name)) {
|
||||
throw new Error(`Pipe name must be unique: "${pipe.name}"`);
|
||||
|
||||
@@ -83,7 +83,7 @@ function findElementFast(
|
||||
win: Cypress.AUTWindow,
|
||||
query: string,
|
||||
handler: (element: Element) => void,
|
||||
timeoutInMs = 5000,
|
||||
timeoutInMs = 10000,
|
||||
): void {
|
||||
const endTime = Date.now() + timeoutInMs;
|
||||
const finder = new ContinuousRunner();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe } from 'vitest';
|
||||
import { EscapeDoubleQuotes } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes';
|
||||
import { getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { runPipeTests } from './PipeTestRunner';
|
||||
import { runPipeTests, type PipeTestScenario } from './PipeTestRunner';
|
||||
|
||||
describe('EscapeDoubleQuotes', () => {
|
||||
// arrange
|
||||
@@ -9,23 +9,23 @@ describe('EscapeDoubleQuotes', () => {
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase) => ({
|
||||
name: 'returns as it is when if input is missing',
|
||||
.map((testCase): PipeTestScenario => ({
|
||||
description: `returns empty when if input is missing (${testCase.valueName})`,
|
||||
input: testCase.absentValue,
|
||||
expectedOutput: testCase.absentValue,
|
||||
expectedOutput: '',
|
||||
})),
|
||||
{
|
||||
name: 'using "',
|
||||
description: 'using "',
|
||||
input: 'hello "world"',
|
||||
expectedOutput: 'hello "^""world"^""',
|
||||
},
|
||||
{
|
||||
name: 'not using any double quotes',
|
||||
description: 'not using any double quotes',
|
||||
input: 'hello world',
|
||||
expectedOutput: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'consecutive double quotes',
|
||||
description: 'consecutive double quotes',
|
||||
input: '""hello world""',
|
||||
expectedOutput: '"^"""^""hello world"^"""^""',
|
||||
},
|
||||
|
||||
@@ -1,465 +1,50 @@
|
||||
import { describe } from 'vitest';
|
||||
import { InlinePowerShell } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipeDefinitions/InlinePowerShell';
|
||||
import { getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { type IPipeTestCase, runPipeTests } from './PipeTestRunner';
|
||||
import { runPipeTests, type PipeTestScenario } from './PipeTestRunner';
|
||||
import { createTryCatchFinallyTests } from './InlinePowerShellTests/CreateTryCatchFinallyTests';
|
||||
import { createAbsentCodeTests } from './InlinePowerShellTests/CreateAbsentCodeTests';
|
||||
import { createCommentedCodeTests } from './InlinePowerShellTests/CreateCommentedCodeTests';
|
||||
import { createIfStatementTests } from './InlinePowerShellTests/CreateIfStatementTests';
|
||||
import { createLineContinuationBacktickCases } from './InlinePowerShellTests/CreateLineContinuationBacktickTests';
|
||||
import { createDoWhileTests } from './InlinePowerShellTests/CreateDoWhileTests';
|
||||
import { createDoUntilTests } from './InlinePowerShellTests/CreateDoUntilTests';
|
||||
import { createForeachTests } from './InlinePowerShellTests/CreateForeachTests';
|
||||
import { createWhileTests } from './InlinePowerShellTests/CreateWhileTests';
|
||||
import { createForLoopTests } from './InlinePowerShellTests/CreateForLoopTests';
|
||||
import { createSwitchTests } from './InlinePowerShellTests/CreateSwitchTests';
|
||||
import { createHereStringTests } from './InlinePowerShellTests/CreateHereStringTests';
|
||||
import { createNewlineTests } from './InlinePowerShellTests/CreateNewlineTests';
|
||||
import { createFunctionTests } from './InlinePowerShellTests/CreateFunctionTests';
|
||||
import { createScriptBlockTests } from './InlinePowerShellTests/CreateScriptBlockTests';
|
||||
|
||||
describe('InlinePowerShell', () => {
|
||||
// arrange
|
||||
const sut = new InlinePowerShell();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase) => ({
|
||||
name: 'returns as it is when if input is missing',
|
||||
input: testCase.absentValue,
|
||||
expectedOutput: '',
|
||||
})),
|
||||
...prefixTests('newline', getNewLineCases()),
|
||||
...prefixTests('comment', getCommentCases()),
|
||||
...prefixTests('here-string', hereStringCases()),
|
||||
...prefixTests('backtick', backTickCases()),
|
||||
...prefixTests('absent code', createAbsentCodeTests()),
|
||||
...prefixTests('newline', createNewlineTests()),
|
||||
...prefixTests('comment', createCommentedCodeTests()),
|
||||
...prefixTests('here-string', createHereStringTests()),
|
||||
...prefixTests('line continuation backtick', createLineContinuationBacktickCases()),
|
||||
...prefixTests('try-catch-finally', createTryCatchFinallyTests()),
|
||||
...prefixTests('if statement', createIfStatementTests()),
|
||||
...prefixTests('do-while loop', createDoWhileTests()),
|
||||
...prefixTests('do-until loop', createDoUntilTests()),
|
||||
...prefixTests('foreach loop', createForeachTests()),
|
||||
...prefixTests('while loop', createWhileTests()),
|
||||
...prefixTests('for loop', createForLoopTests()),
|
||||
...prefixTests('switch statement', createSwitchTests()),
|
||||
...prefixTests('function', createFunctionTests()),
|
||||
...prefixTests('script block', createScriptBlockTests()),
|
||||
]);
|
||||
});
|
||||
|
||||
function hereStringCases(): IPipeTestCase[] {
|
||||
const expectLinesInDoubleQuotes = (...lines: string[]) => lines.join('`r`n');
|
||||
const expectLinesInSingleQuotes = (...lines: string[]) => lines.join('\'+"`r`n"+\'');
|
||||
return [
|
||||
{
|
||||
name: 'adds newlines for double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'"Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds newlines for single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with character after here string header',
|
||||
input: getWindowsLines(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match if there\'s character before here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with different here-string header/terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner single quoted here-string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerDoubleQuotedTerminator = @"',
|
||||
'inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$hasInnerDoubleQuotedTerminator = "inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner double quoted string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerSingleQuotedTerminator = @\'',
|
||||
'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'$hasInnerSingleQuotedTerminator = \'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches if there\'s character after here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'\'@ after',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'lorem\' after',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'escapes double quotes inside double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'For help, type "get-help"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"For help, type `"get-help`""',
|
||||
},
|
||||
{
|
||||
name: 'escapes single quotes inside single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'For help, type \'get-help\'',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: '\'For help, type \'\'get-help\'\'\'',
|
||||
},
|
||||
{
|
||||
name: 'converts when here-string header is not at line start',
|
||||
input: getWindowsLines(
|
||||
'$page = [XML] @"',
|
||||
'multi-lined',
|
||||
'and "quoted"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$page = [XML] "multi-lined',
|
||||
'and `"quoted`""',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims after here-string header',
|
||||
input: getWindowsLines(
|
||||
'@" \t',
|
||||
'text with whitespaces at here-string start',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"text with whitespaces at here-string start"',
|
||||
},
|
||||
{
|
||||
name: 'preserves whitespaces in lines',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around ',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around \'',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function backTickCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick and different line endings',
|
||||
input: 'Get-Service `\n'
|
||||
+ '* `\r'
|
||||
+ '| Sort-Object StartType `\r\n'
|
||||
+ '| Format-Table -AutoSize',
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'trims tabs and whitespaces on next lines when wrapping with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'\t| Sort-Object StartType `',
|
||||
' | Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'does not wrap without whitespace before backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not wrap with characters after',
|
||||
input: getWindowsLines(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getCommentCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'converts hash comments in the line end',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t# Comment after tab',
|
||||
'$text+= #Comment without space after hash',
|
||||
'Write-Host $text# Comment without space before hash',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# Comment after tab #>',
|
||||
'$text+= <# Comment without space after hash #>',
|
||||
'Write-Host $text<# Comment without space before hash #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts hash comment line',
|
||||
input: getWindowsLines(
|
||||
'# Comment in first line',
|
||||
'Write-Host "Hello"',
|
||||
'# Comment in the middle',
|
||||
'Write-Host "World"',
|
||||
'# Consecutive comments',
|
||||
'# Last line comment without line ending in the end',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment in first line #>',
|
||||
'Write-Host "Hello"',
|
||||
'<# Comment in the middle #>',
|
||||
'Write-Host "World"',
|
||||
'<# Consecutive comments #>',
|
||||
'<# Last line comment without line ending in the end #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts inside',
|
||||
input: getWindowsLines(
|
||||
'$text+= #Comment with < inside',
|
||||
'$text+= #Comment ending with >',
|
||||
'$text+= #Comment with <# inline comment #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text+= <# Comment with < inside #>',
|
||||
'$text+= <# Comment ending with > #>',
|
||||
'$text+= <# Comment with <# inline comment #> #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts around', // Pretty uncommon
|
||||
input: getWindowsLines(
|
||||
'Write-Host "hi" # Comment ending line inline comment but not one #>',
|
||||
'Write-Host "hi" #>Comment starting like inline comment end but not one',
|
||||
// Following line does not compile as valid PowerShell due to missing #> for inline comment.
|
||||
'Write-Host "hi" <#Comment starting like inline comment start but not one',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
|
||||
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
|
||||
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts empty hash comment',
|
||||
input: getWindowsLines(
|
||||
'Write-Host "Comment without text" #',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "Comment without text" <##>',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds whitespaces around to match',
|
||||
input: getWindowsLines(
|
||||
'#Comment line with no whitespaces around',
|
||||
'Write-Host "Hello"#Comment in the end with no whitespaces around',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment line with no whitespaces around #>',
|
||||
'Write-Host "Hello"<# Comment in the end with no whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces around comment',
|
||||
input: getWindowsLines(
|
||||
'# Comment with whitespaces around ',
|
||||
'#\tComment with tabs around\t\t',
|
||||
'#\t Comment with tabs and whitespaces around \t \t',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment with whitespaces around #>',
|
||||
'<# Comment with tabs around #>',
|
||||
'<# Comment with tabs and whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not convert block comments',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not process if there are no multi lines',
|
||||
input: 'Write-Host "expected" # as it is!',
|
||||
expectedOutput: 'Write-Host "expected" # as it is!',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getNewLineCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'no new line',
|
||||
input: 'Write-Host \'Hello, World!\'',
|
||||
expectedOutput: 'Write-Host \'Hello, World!\'',
|
||||
},
|
||||
{
|
||||
name: '\\n new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\n double empty lines are ignored',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\n\nforeach ($thing in $things) {'
|
||||
+ '\n\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\n\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\rforeach ($thing in $things) {'
|
||||
+ '\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r and \\n newlines combined',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\r\nforeach ($thing in $things) {'
|
||||
+ '\n\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces on lines',
|
||||
input:
|
||||
' $things = Get-ChildItem C:\\Windows\\ '
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\n\tWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r \n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function prefixTests(prefix: string, tests: IPipeTestCase[]): IPipeTestCase[] {
|
||||
function prefixTests(prefix: string, tests: PipeTestScenario[]): PipeTestScenario[] {
|
||||
return tests.map((test) => ({
|
||||
name: `[${prefix}] ${test.name}`,
|
||||
input: test.input,
|
||||
expectedOutput: test.expectedOutput,
|
||||
...test,
|
||||
...{
|
||||
description: `[${prefix}] ${test.description}`,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function getWindowsLines(...lines: string[]) {
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
|
||||
function getSingleLinedOutput(...lines: string[]) {
|
||||
return lines.map((line) => line.trim()).join('; ');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { RegexBuilder } from '../PipeTestRunner';
|
||||
|
||||
export function joinAsWindowsLines(
|
||||
...lines: string[]
|
||||
): string {
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a relaxed regular expression pattern for matching inlined multiple lines of code
|
||||
* with basic semicolon merging.
|
||||
*/
|
||||
export function getInlinedOutputWithSemicolons(
|
||||
...lines: string[]
|
||||
): RegExp {
|
||||
const trimmedLines = lines.map((line) => line.trim());
|
||||
const builder = new RegexBuilder();
|
||||
trimmedLines.forEach((line, index) => {
|
||||
builder.withLiteralString(line);
|
||||
builder.withOptionalSemicolon(); // Semi colon at the end compiles fine
|
||||
if (index !== trimmedLines.length - 1) {
|
||||
builder.withOptionalWhitespaceButNoNewline();
|
||||
}
|
||||
});
|
||||
return builder.buildRegex();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { getAbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import type { PipeTestScenario } from '../PipeTestRunner';
|
||||
|
||||
export function createAbsentCodeTests(): PipeTestScenario[] {
|
||||
return [
|
||||
...getAbsentStringTestCases({ excludeNull: true, excludeUndefined: true })
|
||||
.map((testCase): PipeTestScenario => ({
|
||||
description: `absent string (${testCase.valueName})`,
|
||||
input: testCase.absentValue,
|
||||
expectedOutput: '',
|
||||
})),
|
||||
{
|
||||
description: 'whitespace-only input',
|
||||
input: ' \t\n\r\f\v\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000',
|
||||
expectedOutput: '',
|
||||
},
|
||||
{
|
||||
description: 'newline-only input',
|
||||
input: '\n\r\u2028\u2029',
|
||||
expectedOutput: '',
|
||||
},
|
||||
{
|
||||
description: 'newline-only input',
|
||||
input: ' \t\n\r\f\v\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\n\r\u2028\u2029',
|
||||
expectedOutput: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { getInlinedOutputWithSemicolons, joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
import type { PipeTestScenario } from '../PipeTestRunner';
|
||||
|
||||
export function createCommentedCodeTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help?view=powershell-7.4
|
||||
return [
|
||||
{
|
||||
description: 'converts hash comments at line end',
|
||||
input: joinAsWindowsLines(
|
||||
'$text = "Hello"\t# Comment after tab',
|
||||
'$text+= #Comment without space after hash',
|
||||
'Write-Host $text# Comment without space before hash',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$text = "Hello"\t<# Comment after tab #>',
|
||||
'$text+= <# Comment without space after hash #>',
|
||||
'Write-Host $text<# Comment without space before hash #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'converts hash comment lines',
|
||||
input: joinAsWindowsLines(
|
||||
'# Comment in first line',
|
||||
'Write-Host "Hello"',
|
||||
'# Comment in the middle',
|
||||
'Write-Host "World"',
|
||||
'# Consecutive comments',
|
||||
'# Last line comment without line ending in the end',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'<# Comment in first line #>',
|
||||
'Write-Host "Hello"',
|
||||
'<# Comment in the middle #>',
|
||||
'Write-Host "World"',
|
||||
'<# Consecutive comments #>',
|
||||
'<# Last line comment without line ending in the end #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'converts comments with inline comment parts inside',
|
||||
input: joinAsWindowsLines(
|
||||
'$text+= #Comment with < inside',
|
||||
'$text+= #Comment ending with >',
|
||||
'$text+= #Comment with <# inline comment #>',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$text+= <# Comment with < inside #>',
|
||||
'$text+= <# Comment ending with > #>',
|
||||
'$text+= <# Comment with <# inline comment #> #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'converts comments with inline comment parts around', // Pretty uncommon
|
||||
input: joinAsWindowsLines(
|
||||
'Write-Host "hi" # Comment ending line inline comment but not one #>',
|
||||
'Write-Host "hi" #>Comment starting like inline comment end but not one',
|
||||
// Following line does not compile as valid PowerShell due to missing #> for inline comment.
|
||||
'Write-Host "hi" <#Comment starting like inline comment start but not one',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
|
||||
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
|
||||
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'converts empty hash comments',
|
||||
input: joinAsWindowsLines(
|
||||
'Write-Host "Comment without text" #',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'Write-Host "Comment without text" <##>',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'adds whitespaces around comments',
|
||||
input: joinAsWindowsLines(
|
||||
'#Comment line with no whitespaces around',
|
||||
'Write-Host "Hello"#Comment in the end with no whitespaces around',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'<# Comment line with no whitespaces around #>',
|
||||
'Write-Host "Hello"<# Comment in the end with no whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'trims whitespaces around comments',
|
||||
input: joinAsWindowsLines(
|
||||
'# Comment with whitespaces around ',
|
||||
'#\tComment with tabs around\t\t',
|
||||
'#\t Comment with tabs and whitespaces around \t \t',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'<# Comment with whitespaces around #>',
|
||||
'<# Comment with tabs around #>',
|
||||
'<# Comment with tabs and whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves block comments',
|
||||
input: joinAsWindowsLines(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves single-line input',
|
||||
input: 'Write-Host "expected" # as it is!',
|
||||
expectedOutput: 'Write-Host "expected" # as it is!',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createDoUntilTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_do?view=powershell-7.4
|
||||
return [
|
||||
{
|
||||
description: 'do-until loop without newlines',
|
||||
input: 'do { $i++; Write-Host $i } until ($i -ge 5)',
|
||||
expectedOutput: 'do { $i++; Write-Host $i } until ($i -ge 5)',
|
||||
},
|
||||
{
|
||||
description: 'simple do-until loop (single line inside do block)',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
'} until ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'multiline do-until loop',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' Write-Host $i',
|
||||
'} until ($i -ge 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -ge 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested do-until loops',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $outer = 0',
|
||||
' do {',
|
||||
' $inner = 0',
|
||||
' do {',
|
||||
' $inner++',
|
||||
' Write-Host "Inner: $inner"',
|
||||
' } until ($inner -ge 3)',
|
||||
' $outer++',
|
||||
' Write-Host "Outer: $outer"',
|
||||
' } until ($outer -ge 2)',
|
||||
' $mainCounter++',
|
||||
' Write-Host "Main: $mainCounter"',
|
||||
'} until ($mainCounter -ge 2)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$outer = 0')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$inner = 0')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$inner++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Inner: $inner"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($inner -ge 3)')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$outer++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Outer: $outer"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($outer -ge 2)')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$mainCounter++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Main: $mainCounter"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($mainCounter -ge 2)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-until loop with condition on separate line',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' Write-Host $i',
|
||||
'}',
|
||||
'until ($i -ge 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}') // No semicolon after this to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -ge 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-until loop with complex condition',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' $j--',
|
||||
'} until ( `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' $i -ge 10 -or `',
|
||||
' $j -le 0 `',
|
||||
')',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$j--')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i -ge 10 -or')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$j -le 0')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-until loop with nested if statement',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' if ($i % 2 -eq 0) {',
|
||||
' Write-Host "Even: $i"',
|
||||
' } else {',
|
||||
' Write-Host "Odd: $i"',
|
||||
' }',
|
||||
'} until ($i -ge 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('if ($i % 2 -eq 0)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Even: $i"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('else')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Odd: $i"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -ge 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-until loop with semicolon after closing brace',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' Write-Host $i',
|
||||
'};',
|
||||
'until ($i -ge 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -ge 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: '-until loop with pipeline in condition',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $result = Get-Something',
|
||||
' Process-Result $result',
|
||||
'} until ($result | Test-Condition)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = Get-Something')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Process-Result $result')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result | Test-Condition')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-until loop with multiline condition',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $result = Get-Something',
|
||||
' Process-Result $result',
|
||||
'} until ( `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' $result -and (-Not $result) `',
|
||||
')',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = Get-Something')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Process-Result $result')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result -and (-Not $result)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-until loop with script block condition',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' Write-Host $i',
|
||||
'} until ( `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' & {',
|
||||
' param($val)',
|
||||
' $val -ge 5',
|
||||
' } $i `',
|
||||
')',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('until')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('&')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param($val)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$val -ge 5')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-until after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'switch ($value) { default { Write-Host "Default" } }',
|
||||
'do { $i++ } until ($i -ge 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('switch ($value) { default { Write-Host "Default" } }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('do { $i++ } until ($i -ge 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createDoWhileTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_do?view=powershell-7.4
|
||||
return [
|
||||
{
|
||||
description: 'do-while loop without newlines',
|
||||
input: 'do { $i++ } while ($i -lt 5)',
|
||||
expectedOutput: 'do { $i++ } while ($i -lt 5)',
|
||||
},
|
||||
{
|
||||
description: 'simple do-while loop (single line inside do block)',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
'} while ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'multiline do-while loop',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' Write-Host $i',
|
||||
'} while ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested do-while loops',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' do {',
|
||||
' $j++',
|
||||
' } while ($j -lt 3)',
|
||||
' Write-Host "$i, $j"',
|
||||
'} while ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$j++')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($j -lt 3)')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "$i, $j"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-while loop with condition on separate line',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
'}',
|
||||
'while ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}') // No semicolon after this to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-while loop with multiline condition',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' $j--',
|
||||
'} while ( `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' $i -lt 10 -and `',
|
||||
' $j -gt 0 `',
|
||||
')',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$j--')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i -lt 10 -and')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$j -gt 0')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-while loop with nested if statement',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
' if ($i % 2 -eq 0) {',
|
||||
' Write-Host "Even"',
|
||||
' } else {',
|
||||
' Write-Host "Odd"',
|
||||
' }',
|
||||
'} while ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('if ($i % 2 -eq 0)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Even"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('else')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Odd"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-while loop with semicolon after closing brace',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $i++',
|
||||
'};',
|
||||
'while ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-while loop with pipeline in condition',
|
||||
input: joinAsWindowsLines(
|
||||
'do {',
|
||||
' $result = Get-Something',
|
||||
'} while ( $result | Test-Condition )',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('do')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = Get-Something')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result | Test-Condition')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'do-while after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($someCondition) { $variable = "Some value" }',
|
||||
'do { $i++; Write-Host $i } while ($i -lt 5)',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if ($someCondition) { $variable = "Some value" }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('do { $i++; Write-Host $i } while ($i -lt 5)')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createForLoopTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_for?view=powershell-7.4
|
||||
// Known limitations:
|
||||
// - Multiline for loop with sections without semicolon, e.g. `for(\n$i =0\n$i - l5\n$i++\n)`
|
||||
return [
|
||||
{
|
||||
description: 'for loop without newlines',
|
||||
input: 'for ($i = 0; $i -lt 5; $i++) { Write-Host $i }',
|
||||
expectedOutput: 'for ($i = 0; $i -lt 5; $i++) { Write-Host $i }',
|
||||
},
|
||||
{
|
||||
description: 'simple for loop (single line inside code block)',
|
||||
input: joinAsWindowsLines(
|
||||
'for ($i = 0; $i -lt 5; $i++) {',
|
||||
' Write-Host $i',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('for')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i = 0; $i -lt 5; $i++)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'multiline for loop',
|
||||
input: joinAsWindowsLines(
|
||||
'for ($i = 0; $i -lt 5; $i++) {',
|
||||
' Write-Host "Current value: $i"',
|
||||
' $result += $i',
|
||||
' Do-SomethingWith $i',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('for')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i = 0; $i -lt 5; $i++)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Current value: $i"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result += $i')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Do-SomethingWith $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested for loops',
|
||||
input: joinAsWindowsLines(
|
||||
'for ($i = 0; $i -lt 3; $i++) {',
|
||||
' for ($j = 0; $j -lt 2; $j++) {',
|
||||
' Write-Host "i: $i, j: $j"',
|
||||
' }',
|
||||
' Write-Host "Outer loop: $i"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('for')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i = 0; $i -lt 3; $i++)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('for')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($j = 0; $j -lt 2; $j++)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "i: $i, j: $j"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Outer loop: $i"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'for loop without spaces in header',
|
||||
input: joinAsWindowsLines(
|
||||
'for($i=0;$i-lt5;$i++){',
|
||||
' Write-Host $i',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('for')
|
||||
.withLiteralString('($i=0;$i-lt5;$i++)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'for loop with empty sections',
|
||||
input: joinAsWindowsLines(
|
||||
'for (;;) {',
|
||||
' $i++',
|
||||
' if ($i -ge 5) { break }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('for')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(;;)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('if ($i -ge 5) { break }')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'for loop with multiple statements in each section',
|
||||
input: joinAsWindowsLines(
|
||||
'for ($i = 0, $j = 10; $i -lt 5 -and $j -gt 0; $i++, $j--) {',
|
||||
' Write-Host "i: $i, j: $j"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('for')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i = 0, $j = 10; $i -lt 5 -and $j -gt 0; $i++, $j--)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "i: $i, j: $j"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'for loop with multiline sections',
|
||||
input: joinAsWindowsLines(
|
||||
'for ( `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' $i = 0; `',
|
||||
' $i -lt 5; `',
|
||||
' $i++ `',
|
||||
') {',
|
||||
' Write-Host $i',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('for')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i = 0;')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i -lt 5;')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $i')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'for after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'$scriptBlock = { Write-Host "Inside script block" }',
|
||||
'for ($i = 0; $i -lt 5; $i++) { Write-Host $i }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('$scriptBlock = { Write-Host "Inside script block" }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('for ($i = 0; $i -lt 5; $i++) { Write-Host $i }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createForeachTests(): PipeTestScenario[] {
|
||||
return [
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_foreach?view=powershell-7.4
|
||||
{
|
||||
description: 'foreach loop without newlines',
|
||||
input: 'foreach ($item in $collection) { Write-Host $item }',
|
||||
expectedOutput: 'foreach ($item in $collection) { Write-Host $item }',
|
||||
},
|
||||
{
|
||||
description: 'simple foreach loop (single line inside code block)',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ($item in $collection) {',
|
||||
' Write-Host $item',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($item in $collection)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $item')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'multiline foreach loop',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ($item in $collection) {',
|
||||
' $processedItem = $item.ToUpper()',
|
||||
' Write-Host "Processing: $processedItem"',
|
||||
' $result += $processedItem',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($item in $collection)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$processedItem = $item.ToUpper()')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Processing: $processedItem"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result += $processedItem')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested foreach loops',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ($outer in $outerCollection) {',
|
||||
' Write-Host "Outer: $outer"',
|
||||
' foreach ($inner in $innerCollection) {',
|
||||
' Write-Host " Inner: $inner"',
|
||||
' $result = "$outer-$inner"',
|
||||
' $combinedResults += $result',
|
||||
' }',
|
||||
' Write-Host "Completed inner loop for $outer"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($outer in $outerCollection)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Outer: $outer"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($inner in $innerCollection)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host " Inner: $inner"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = "$outer-$inner"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$combinedResults += $result')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Completed inner loop for $outer"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'foreach loop with multiline condition',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ( `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' $item `',
|
||||
' in `',
|
||||
' $collection `',
|
||||
') {',
|
||||
' Write-Host $item',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$item')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('in')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$collection')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $item')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'foreach loop with pipeline in collection',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ($item in Get-Process | Where-Object { $_.CPU -gt 50 }) {',
|
||||
' Write-Host $item.Name',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($item in Get-Process |')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Where-Object { $_.CPU -gt 50 })')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $item.Name')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'foreach loop with complex collection expression',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ($item in ( `',
|
||||
' $array1 + `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' $array2 | `',
|
||||
' Where-Object { $_ -ne $null } `',
|
||||
')) {',
|
||||
' Write-Host $item',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($item in (')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$array1 +')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$array2 |')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Where-Object { $_ -ne $null }')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('))')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $item')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'foreach loop with script block in collection',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ($item in & {',
|
||||
' param($start, $end)',
|
||||
' $start..$end',
|
||||
'} 1 10) {',
|
||||
' Write-Host $item',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($item in &')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param($start, $end)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$start..$end')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('1 10)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $item')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'foreach loop with closing brace on same line as last statement',
|
||||
input: joinAsWindowsLines(
|
||||
'foreach ($item in $collection) {',
|
||||
' Write-Host $item',
|
||||
' Process-Item $item }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('foreach')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($item in $collection)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $item')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Process-Item $item')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'foreach after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'function Test-Function { Write-Host "Test" }',
|
||||
'foreach ($item in @(1,2,3)) { Write-Host $item }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('function Test-Function { Write-Host "Test" }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('foreach ($item in @(1,2,3)) { Write-Host $item }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createFunctionTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions?view=powershell-7.4
|
||||
// Known limitations:
|
||||
// - Functions with advanced parameters are not yet supported.
|
||||
return [
|
||||
{
|
||||
description: 'function without newlines',
|
||||
input: 'function Get-Name { param($FirstName, $LastName) Write-Output "$FirstName $LastName" }',
|
||||
expectedOutput: 'function Get-Name { param($FirstName, $LastName) Write-Output "$FirstName $LastName" }',
|
||||
},
|
||||
{
|
||||
description: 'simple function (single line inside code block)',
|
||||
input: joinAsWindowsLines(
|
||||
'function Say-Hello {',
|
||||
' Write-Host "Hello, World!"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Say-Hello')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Hello, World!"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'function with multiple statements',
|
||||
input: joinAsWindowsLines(
|
||||
'function Do-Something {',
|
||||
' $result = Get-Something',
|
||||
' Process-Result $result',
|
||||
' Write-Output "Done processing"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Do-Something')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = Get-Something')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Process-Result $result')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Output "Done processing"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'function with begin, process, and end blocks',
|
||||
input: joinAsWindowsLines(
|
||||
'function Process-Collection {',
|
||||
' begin {',
|
||||
' $total = 0',
|
||||
' }',
|
||||
' process {',
|
||||
' $total += $_',
|
||||
' }',
|
||||
' end {',
|
||||
' Write-Output "Total: $total"',
|
||||
' }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Process-Collection')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('begin')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$total = 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('process')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$total += $_')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('end')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Output "Total: $total"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested functions',
|
||||
input: joinAsWindowsLines(
|
||||
'function Outer-Function {',
|
||||
' param($outerParam)',
|
||||
' function Inner-Function {',
|
||||
' param($innerParam)',
|
||||
' Write-Output "Inner: $innerParam"',
|
||||
' }',
|
||||
' Write-Output "Outer: $outerParam"',
|
||||
' Inner-Function -innerParam "Hello from inner"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Outer-Function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param($outerParam)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Inner-Function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param($innerParam)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Output "Inner: $innerParam"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Output "Outer: $outerParam"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Inner-Function -innerParam "Hello from inner"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'function without space before opening brace',
|
||||
input: joinAsWindowsLines(
|
||||
'function Get-Something{',
|
||||
' return "Something"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Get-Something')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('return "Something"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'function with single-line param block',
|
||||
input: joinAsWindowsLines(
|
||||
'function Set-Value {',
|
||||
' param([string]$key, [object]$value)',
|
||||
' $hash[$key] = $value',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('function')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Set-Value')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param([string]$key, [object]$value)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$hash[$key] = $value')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'function after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition) { $value = 10 }',
|
||||
'function Test-Function { param($param) Write-Host $param }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if ($condition) { $value = 10 }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('function Test-Function { param($param) Write-Host $param }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
import { getInlinedOutputWithSemicolons, joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
import type { PipeTestScenario } from '../PipeTestRunner';
|
||||
|
||||
export function createHereStringTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.4#here-strings
|
||||
return [
|
||||
{
|
||||
description: 'double-quoted here-string',
|
||||
input: joinAsWindowsLines(
|
||||
'@"',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
joinLinesForDoubleQuotedString(
|
||||
'"Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet"',
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'single-quoted here-string',
|
||||
input: joinAsWindowsLines(
|
||||
'@\'',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
joinLinesForSingleQuotedString(
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves invalid here-string syntax',
|
||||
input: joinAsWindowsLines(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves here-string with character before terminator',
|
||||
input: joinAsWindowsLines(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves here-string with mismatched delimiters',
|
||||
input: joinAsWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'single quoted here-string with nested single quoted here-string',
|
||||
input: joinAsWindowsLines(
|
||||
'$hasInnerDoubleQuotedTerminator = @"',
|
||||
'inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
joinLinesForDoubleQuotedString(
|
||||
'$hasInnerDoubleQuotedTerminator = "inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@"',
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'single quoted here-string with inner double-quoted string',
|
||||
input: joinAsWindowsLines(
|
||||
'$hasInnerSingleQuotedTerminator = @\'',
|
||||
'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
joinLinesForSingleQuotedString(
|
||||
'$hasInnerSingleQuotedTerminator = \'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@\'',
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'here-string with character after terminator',
|
||||
input: joinAsWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'\'@ after',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'\'lorem\' after',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'escapes double quotes in double-quoted here-string',
|
||||
input: joinAsWindowsLines(
|
||||
'@"',
|
||||
'For help, type "get-help"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'"For help, type `"get-help`""',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'escapes single quotes in single-quoted here-string',
|
||||
input: joinAsWindowsLines(
|
||||
'@\'',
|
||||
'For help, type \'get-help\'',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'\'For help, type \'\'get-help\'\'\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'here-string not at line start',
|
||||
input: joinAsWindowsLines(
|
||||
'$page = [XML] @"',
|
||||
'multi-lined',
|
||||
'and "quoted"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
joinLinesForDoubleQuotedString(
|
||||
'$page = [XML] "multi-lined',
|
||||
'and `"quoted`""',
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'trims whitespace after here-string header',
|
||||
input: joinAsWindowsLines(
|
||||
'@" \t',
|
||||
'text with whitespaces at here-string start',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'"text with whitespaces at here-string start"',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves whitespace in here-string lines',
|
||||
input: joinAsWindowsLines(
|
||||
'@\'',
|
||||
'\ttext with tabs around\t\t',
|
||||
' text with whitespace around ',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
joinLinesForSingleQuotedString(
|
||||
'\'\ttext with tabs around\t\t',
|
||||
' text with whitespace around \'',
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves code inside here-string',
|
||||
input: joinAsWindowsLines( // Triggering a some code inlining logic:
|
||||
'@"',
|
||||
'if (',
|
||||
' $condition1 -and',
|
||||
' $condition2',
|
||||
') {',
|
||||
' Write-Host "True"',
|
||||
' Write-Warning "Not false"',
|
||||
'} else',
|
||||
'{',
|
||||
' Get-Process `',
|
||||
' | Where-Object { $_.CPU -gt 50 }',
|
||||
'}',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
joinLinesForDoubleQuotedString( // Identical to input
|
||||
'"if (',
|
||||
' $condition1 -and',
|
||||
' $condition2',
|
||||
') {',
|
||||
' Write-Host `"True`"',
|
||||
' Write-Warning `"Not false`"',
|
||||
'} else',
|
||||
'{',
|
||||
' Get-Process `',
|
||||
' | Where-Object { $_.CPU -gt 50 }',
|
||||
'}"',
|
||||
),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function joinLinesForDoubleQuotedString(
|
||||
...lines: readonly string[]
|
||||
): string {
|
||||
return lines.join('`r`n');
|
||||
}
|
||||
|
||||
function joinLinesForSingleQuotedString(
|
||||
...lines: readonly string[]
|
||||
): string {
|
||||
return lines.join('\'+"`r`n"+\'');
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createIfStatementTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_if?view=powershell-7.4
|
||||
return [
|
||||
{
|
||||
description: 'if/else statement without newlines',
|
||||
input: 'if ($condition) { Write-Host "True" } else { Write-Host "False" }',
|
||||
expectedOutput: 'if ($condition) { Write-Host "True" } else { Write-Host "False" }',
|
||||
},
|
||||
{
|
||||
description: 'simple if statement (single line inside code block)',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($true) {',
|
||||
' Write-Host "True"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($true)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "True"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if/else statement',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition) {',
|
||||
' Write-Host "True"',
|
||||
'} else {',
|
||||
' Write-Host "False"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "True"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('else')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "False"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if/elseif/else statement',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition1) {',
|
||||
' Write-Host "Condition 1"',
|
||||
'} elseif ($condition2) {',
|
||||
' Write-Host "Condition 2"',
|
||||
'} else {',
|
||||
' Write-Host "None"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition1)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Condition 1"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('elseif')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition2)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Condition 2"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('else')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "None"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if statement with multiple lines',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition) {',
|
||||
' $result = 10 * 5',
|
||||
' Write-Host "Calculation done"',
|
||||
' $finalResult = $result + 20',
|
||||
' Write-Host "Final result: $finalResult"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 * 5')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Calculation done"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$finalResult = $result + 20')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Final result: $finalResult"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested if statements',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($outerCondition) {',
|
||||
' $outerResult = 10',
|
||||
' if ($innerCondition1) {',
|
||||
' Write-Host "Inner condition 1 met"',
|
||||
' } elseif ($innerCondition2) {',
|
||||
' Write-Host "Inner condition 2 met"',
|
||||
' } else {',
|
||||
' Write-Host "No inner conditions met"',
|
||||
' }',
|
||||
' Write-Host "Outer condition processing complete"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($outerCondition)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$outerResult = 10')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($innerCondition1)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Inner condition 1 met"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('elseif')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($innerCondition2)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Inner condition 2 met"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('else')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "No inner conditions met"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Outer condition processing complete"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if/elseif/else with closing brackets on separate lines',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition1) {',
|
||||
' Write-Host "Condition 1"',
|
||||
'}',
|
||||
'elseif ($condition2) {',
|
||||
' Write-Host "Condition 2"',
|
||||
'}',
|
||||
'else {',
|
||||
' Write-Host "No condition met"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition1)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Condition 1"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}') // No semicolon after this to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('elseif')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition2)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Condition 2"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}') // No semicolon after this to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('else')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "No condition met"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if statement without space before opening parenthesis',
|
||||
input: joinAsWindowsLines(
|
||||
'if($condition) {',
|
||||
' Write-Host "True"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if($condition)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "True"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if statement without space after closing parenthesis',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition){',
|
||||
' Write-Host "True"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if ($condition)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "True"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if statement with extra spaces in condition',
|
||||
input: joinAsWindowsLines(
|
||||
'if ( $condition ) {',
|
||||
' Write-Host "True"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$condition')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "True"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if statement with multiline condition',
|
||||
input: joinAsWindowsLines(
|
||||
'if ( `', // Marked: inline-conditions | Merging multiline conditions is not yet supported, so using backticks
|
||||
' $condition1 -and `',
|
||||
' $condition2 `',
|
||||
') {',
|
||||
' Write-Host "True"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$condition1 -and')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$condition2')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString(')')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "True"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if/elseif/else with mixed brace styles',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition1) {',
|
||||
' Write-Host "Condition 1"',
|
||||
'} elseif ($condition2)',
|
||||
'{',
|
||||
' Write-Host "Condition 2"',
|
||||
'} else {',
|
||||
' Write-Host "None"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition1)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Condition 1"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('elseif')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition2)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Condition 2"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('else')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "None"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if statement with pipeline in condition',
|
||||
input: joinAsWindowsLines(
|
||||
'if (Get-Process | Where-Object { $_.CPU -gt 50 }) {',
|
||||
' Write-Host "High CPU usage detected"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('(Get-Process |')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Where-Object { $_.CPU -gt 50 })')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "High CPU usage detected"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'if statement after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'$testArray = @(1, 2, 3, 4, 5)',
|
||||
'$result = $testArray | ForEach-Object { $_ * 2 }',
|
||||
'if ($result.Count -gt 0) { Write-Host "Array has items" }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('$testArray = @(1, 2, 3, 4, 5)')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = $testArray | ForEach-Object { $_ * 2 }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('if ($result.Count -gt 0) { Write-Host "Array has items" }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { getInlinedOutputWithSemicolons, joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
import type { PipeTestScenario } from '../PipeTestRunner';
|
||||
|
||||
export function createLineContinuationBacktickCases(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing?view=powershell-7.4#line-continuation
|
||||
return [
|
||||
{
|
||||
description: 'inlines newlines with trailing backtick',
|
||||
input: joinAsWindowsLines(
|
||||
'Get-Service * `',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'Get-Service * | Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'inlines newlines with trailing backtick and different line endings',
|
||||
input: 'Get-Service `\n'
|
||||
+ '* `\r'
|
||||
+ '| Sort-Object StartType `\r\n'
|
||||
+ '| Format-Table -AutoSize',
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'trims whitespace when inlining with trailing backtick',
|
||||
input: joinAsWindowsLines(
|
||||
'Get-Service * `',
|
||||
'\t| Sort-Object StartType `',
|
||||
' | Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves line without whitespace before backtick',
|
||||
input: joinAsWindowsLines(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'preserves line with characters after backtick',
|
||||
input: joinAsWindowsLines(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { getInlinedOutputWithSemicolons, joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
import type { PipeTestScenario } from '../PipeTestRunner';
|
||||
|
||||
export function createNewlineTests(): PipeTestScenario[] {
|
||||
return [
|
||||
{
|
||||
description: 'does not add semicolon to single line input',
|
||||
input: 'Write-Host "Single line input"',
|
||||
expectedOutput: 'Write-Host "Single line input"',
|
||||
},
|
||||
{
|
||||
description: 'does not add semicolon to last line of multiline input',
|
||||
input: joinAsWindowsLines(
|
||||
'Write-Host "First line"',
|
||||
'Write-Host "Second line"',
|
||||
),
|
||||
expectedOutput: 'Write-Host "First line"; Write-Host "Second line"',
|
||||
},
|
||||
{
|
||||
description: 'preserves existing semicolons',
|
||||
input: joinAsWindowsLines(
|
||||
'line-without-semicolon',
|
||||
'line-with-semicolon;',
|
||||
'ending-line',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'line-without-semicolon',
|
||||
'line-with-semicolon',
|
||||
'ending-line',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'inlines code with \\n newlines',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n}',
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'removes empty lines',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\n\nforeach ($thing in $things) {'
|
||||
+ '\n\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\n\n}',
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'inlines code with \\r newlines',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\rforeach ($thing in $things) {'
|
||||
+ '\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r}',
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'inlines code with mixed newline types',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\r\nforeach ($thing in $things) {'
|
||||
+ '\n\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\r}',
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'trims whitespace from lines',
|
||||
input:
|
||||
' $things = Get-ChildItem C:\\Windows\\ '
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\n\tWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r \n}',
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createScriptBlockTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks%3Fview=powershell-7.4
|
||||
return [
|
||||
{
|
||||
description: 'simple script block without newlines',
|
||||
input: '{ Write-Host "Hello, World!" }',
|
||||
expectedOutput: '{ Write-Host "Hello, World!" }',
|
||||
},
|
||||
{
|
||||
description: 'multiline script block',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' $result = 10 * 5',
|
||||
' Write-Host "The result is: $result"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 * 5')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "The result is: $result"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'script block with parameters',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' param($p1, $p2)',
|
||||
' Write-Host "p1: $p1, p2: $p2"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param($p1, $p2)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "p1: $p1, p2: $p2"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'script block with typed parameters',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' param([int]$p1, [string]$p2)',
|
||||
' Write-Host "p1: $p1, p2: $p2"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param([int]$p1, [string]$p2)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "p1: $p1, p2: $p2"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'script block with begin, process, and end blocks',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' begin { $total = 0 }',
|
||||
' process { $total += $_ }',
|
||||
' end { Write-Host "Total: $total" }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('begin')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$total = 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('process')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$total += $_')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('end')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Total: $total"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested script blocks',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' $innerBlock = {',
|
||||
' param($x)',
|
||||
' Write-Host "Inner: $x"',
|
||||
' }',
|
||||
' & $innerBlock -x "Hello"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$innerBlock =')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('param($x)')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Inner: $x"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('& $innerBlock -x "Hello"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'script block with return statement',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' $result = 10 * 5',
|
||||
' return $result',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 * 5')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('return $result')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'script block with pipeline input',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' process {',
|
||||
' $_ | Where-Object { $_ % 2 -eq 0 }',
|
||||
' }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('process')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$_ | Where-Object { $_ % 2 -eq 0 }')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'script block with dynamicparam block',
|
||||
input: joinAsWindowsLines(
|
||||
'{',
|
||||
' dynamicparam {',
|
||||
' $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary',
|
||||
' return $paramDictionary',
|
||||
' }',
|
||||
' process {',
|
||||
' Write-Host "Processing"',
|
||||
' }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('dynamicparam')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('return $paramDictionary')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('process')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Processing"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'script block after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition) { $value = 10 }',
|
||||
'$scriptBlock = { param($x) Write-Host $x }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if ($condition) { $value = 10 }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$scriptBlock = { param($x) Write-Host $x }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createSwitchTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_switch?view=powershell-7.4
|
||||
return [
|
||||
{
|
||||
description: 'switch statement with no newlines',
|
||||
input: 'switch ($value) { 1 { Write-Host "One" }; 2 { Write-Host "Two" }; default { Write-Host "Other" } }',
|
||||
expectedOutput: 'switch ($value) { 1 { Write-Host "One" }; 2 { Write-Host "Two" }; default { Write-Host "Other" } }',
|
||||
},
|
||||
{
|
||||
description: 'simple switch statement (single line inside code block)',
|
||||
input: joinAsWindowsLines(
|
||||
'switch ($value) {',
|
||||
' 1 { Write-Host "One" }',
|
||||
' 2 { Write-Host "Two" }',
|
||||
' default { Write-Host "Other" }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('switch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($value)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('1')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "One"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('2')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Two"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('default')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Other"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'multiline switch statement',
|
||||
input: joinAsWindowsLines(
|
||||
'switch ($value) {',
|
||||
' 1 {',
|
||||
' Write-Host "One"',
|
||||
' $result = 1',
|
||||
' }',
|
||||
' 2 {',
|
||||
' Write-Host "Two"',
|
||||
' $result = 2',
|
||||
' }',
|
||||
' default {',
|
||||
' Write-Host "Other"',
|
||||
' $result = 0',
|
||||
' }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('switch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($value)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('1')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "One"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 1')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('2')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Two"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 2')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('default')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Other"')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested switch statements',
|
||||
input: joinAsWindowsLines(
|
||||
'switch ($outer) {',
|
||||
' 1 {',
|
||||
' switch ($inner) {',
|
||||
' "A" { Write-Host "1A" }',
|
||||
' "B" { Write-Host "1B" }',
|
||||
' }',
|
||||
' }',
|
||||
' 2 {',
|
||||
' switch ($inner) {',
|
||||
' "A" { Write-Host "2A" }',
|
||||
' "B" { Write-Host "2B" }',
|
||||
' }',
|
||||
' }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('switch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($outer)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('1')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('switch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($inner)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('"A"')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "1A"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('"B"')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "1B"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('2')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('switch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($inner)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('"A"')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "2A"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('"B"')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "2B"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'switch statement with no spaces',
|
||||
input: joinAsWindowsLines(
|
||||
'switch($value){1{Write-Host"One"}2{Write-Host"Two"}default{Write-Host"Other"}}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('switch')
|
||||
.withLiteralString('($value)')
|
||||
.withLiteralString('{')
|
||||
.withLiteralString('1')
|
||||
.withLiteralString('{')
|
||||
.withLiteralString('Write-Host"One"')
|
||||
.withOptionalSemicolon()
|
||||
.withLiteralString('}')
|
||||
.withLiteralString('2')
|
||||
.withLiteralString('{')
|
||||
.withLiteralString('Write-Host"Two"')
|
||||
.withOptionalSemicolon()
|
||||
.withLiteralString('}')
|
||||
.withLiteralString('default')
|
||||
.withLiteralString('{')
|
||||
.withLiteralString('Write-Host"Other"')
|
||||
.withOptionalSemicolon()
|
||||
.withLiteralString('}')
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'switch statement with regex matches',
|
||||
input: joinAsWindowsLines(
|
||||
'switch -Regex ($value) {',
|
||||
' "^A.*" { Write-Host "Starts with A" }',
|
||||
' ".*Z$" { Write-Host "Ends with Z" }',
|
||||
' Default { Write-Host "No match" }',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('switch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('-Regex')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($value)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('"^A.*"')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Starts with A"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('".*Z$"')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Ends with Z"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Default')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "No match"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'switch after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition) { $value = 10 }',
|
||||
'switch ($value) { 1 { "One" } 2 { "Two" } default { "Other" } }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if ($condition) { $value = 10 }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('switch ($value) { 1 { "One" } 2 { "Two" } default { "Other" } }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { getInlinedOutputWithSemicolons, joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createTryCatchFinallyTests(): PipeTestScenario[] {
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.4
|
||||
return [
|
||||
{
|
||||
description: 'try/catch/finally block without newlines',
|
||||
input: 'try { $result = 10 / 0 } catch { Write-Host "An error occurred" } finally { Write-Host "Cleanup" }',
|
||||
expectedOutput: 'try { $result = 10 / 0 } catch { Write-Host "An error occurred" } finally { Write-Host "Cleanup" }',
|
||||
},
|
||||
{
|
||||
description: 'simple try/catch block (single line inside code blocks)',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' $result = 10 / 0',
|
||||
'} catch {',
|
||||
' Write-Warning "An error occurred"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 / 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch {')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Warning "An error occurred"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'multiline try/catch block',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' $result = 10 / 0',
|
||||
' Write-Host "Succesfully completed"',
|
||||
'} catch {',
|
||||
' Write-Warning "An error occurred"',
|
||||
' Write-Host "Wrong division?"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 / 0')
|
||||
.withSemicolon() // Ensure it adds semicolon to multiline text
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Succesfully completed"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch {')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Warning "An error occurred"')
|
||||
.withSemicolon() // Ensure it adds semicolon to multiline text
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Wrong division?"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/finally block',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' $result = 10 / 0',
|
||||
'} finally {',
|
||||
' Write-Warning "An error occurred"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 / 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('finally {')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Warning "An error occurred"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/catch/finally block',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' $result = 10 / 0',
|
||||
'} catch {',
|
||||
' Write-Host "An error occurred"',
|
||||
'} finally {',
|
||||
' Write-Host "Cleanup"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 / 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch {')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "An error occurred"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('finally {')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Cleanup"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/catch/finally with closing brackets on separate lines',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' $result = 10 / 0',
|
||||
'}',
|
||||
'catch {',
|
||||
' Write-Host "An error occurred"',
|
||||
'}',
|
||||
'finally {',
|
||||
' Write-Host "Cleanup"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 / 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline() // No semicolon after this to prevent runtime errors
|
||||
.withLiteralString('catch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "An error occurred"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline() // No semicolon after this to prevent runtime errors
|
||||
.withLiteralString('finally')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Cleanup"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/catch with empty catch block',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' $result = 10 / 0',
|
||||
'} catch {}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 / 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch {}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: '/catch/finally with empty blocks',
|
||||
input: joinAsWindowsLines(
|
||||
'try {} catch {} finally {}',
|
||||
),
|
||||
expectedOutput: getInlinedOutputWithSemicolons(
|
||||
'try {} catch {} finally {}',
|
||||
),
|
||||
},
|
||||
{
|
||||
description: 'try/catch with specific exception type',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' throw [System.DivideByZeroException]::new()',
|
||||
'} catch [System.DivideByZeroException] {',
|
||||
' Write-Host "Caught divide by zero exception"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('throw [System.DivideByZeroException]::new()')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch [System.DivideByZeroException]')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Caught divide by zero exception"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/catch with multiple specific exception types',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' throw [System.IO.FileNotFoundException]::new()',
|
||||
'} catch [System.IO.FileNotFoundException], [System.IO.DirectoryNotFoundException] {',
|
||||
' Write-Host "Caught file or directory not found exception"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('throw [System.IO.FileNotFoundException]::new()')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch [System.IO.FileNotFoundException], [System.IO.DirectoryNotFoundException]')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Caught file or directory not found exception"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/catch with multiple catch blocks',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' $result = 10 / 0',
|
||||
'} catch [System.DivideByZeroException] {',
|
||||
' Write-Host "Caught divide by zero exception"',
|
||||
'} catch [System.Exception] {',
|
||||
' Write-Host "Caught general exception"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$result = 10 / 0')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch [System.DivideByZeroException]')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Caught divide by zero exception"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch [System.Exception]')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Caught general exception"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/catch with exception variable',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' throw "Custom error"',
|
||||
'} catch {',
|
||||
' Write-Host "Error: $($_.Exception.Message)"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('throw "Custom error"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Error: $($_.Exception.Message)"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try/catch/finally with return in try block',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' return "Success"',
|
||||
'} catch {',
|
||||
' Write-Host "An error occurred"',
|
||||
'} finally {',
|
||||
' Write-Host "Cleanup"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('return "Success"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "An error occurred"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('finally')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Cleanup"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested try/catch blocks',
|
||||
input: joinAsWindowsLines(
|
||||
'try {',
|
||||
' try {',
|
||||
' throw "Inner exception"',
|
||||
' } catch {',
|
||||
' throw "Outer exception"',
|
||||
' }',
|
||||
'} catch {',
|
||||
' Write-Host "Caught in outer catch: $($_.Exception.Message)"',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('try')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('throw "Inner exception"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('throw "Outer exception"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('catch')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Caught in outer catch: $($_.Exception.Message)"')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'try after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'if ($condition) { $value = 10 }',
|
||||
'try { $result = 10 / $value }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('if ($condition) { $value = 10 }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('try { $result = 10 / $value }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import { RegexBuilder, type PipeTestScenario } from '../PipeTestRunner';
|
||||
import { joinAsWindowsLines } from './CommonInlinePowerShellTestUtilities';
|
||||
|
||||
export function createWhileTests(): PipeTestScenario[] {
|
||||
return [
|
||||
{
|
||||
description: 'while loop without newlines',
|
||||
input: 'while ($val -ne 3) { $val++ Write-Host $val }',
|
||||
expectedOutput: 'while ($val -ne 3) { $val++ Write-Host $val }',
|
||||
},
|
||||
{
|
||||
description: 'simple while loop (single line inside code block)',
|
||||
input: joinAsWindowsLines(
|
||||
'while ($i -lt 5) {',
|
||||
' $i++',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($i -lt 5)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$i++')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'multiline while loop',
|
||||
input: joinAsWindowsLines(
|
||||
'while ($condition) {',
|
||||
' Do-Something',
|
||||
' Write-Host "Processing..."',
|
||||
' $condition = Test-Condition',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($condition)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Do-Something')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host "Processing..."')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$condition = Test-Condition')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'nested while loops',
|
||||
input: joinAsWindowsLines(
|
||||
'while ($outerCondition) {',
|
||||
' $innerCounter = 0',
|
||||
' while ($innerCounter -lt 3) {',
|
||||
' Do-InnerTask',
|
||||
' $innerCounter++',
|
||||
' }',
|
||||
' Do-OuterTask',
|
||||
' $outerCondition = Test-OuterCondition',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($outerCondition)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$innerCounter = 0')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($innerCounter -lt 3)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Do-InnerTask')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$innerCounter++')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Do-OuterTask')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$outerCondition = Test-OuterCondition')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'while loop without space before opening parenthesis',
|
||||
input: joinAsWindowsLines(
|
||||
'while($val -ne 3) {',
|
||||
' $val++',
|
||||
' Write-Host $val',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('while')
|
||||
.withLiteralString('($val -ne 3)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$val++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $val')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'while loop without space after closing parenthesis',
|
||||
input: joinAsWindowsLines(
|
||||
'while ($val -ne 3){',
|
||||
' $val++',
|
||||
' Write-Host $val',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($val -ne 3)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$val++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $val')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'while loop and trims extra spaces before statements',
|
||||
input: joinAsWindowsLines(
|
||||
'while ($val -ne 3) {',
|
||||
' $val++',
|
||||
' Write-Host $val',
|
||||
'}',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($val -ne 3)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$val++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $val')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'while loop with closing brace on same line as last statement',
|
||||
input: joinAsWindowsLines(
|
||||
'while ($val -ne 3) {',
|
||||
' $val++',
|
||||
' Write-Host $val }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('while')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('($val -ne 3)')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('{')
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('$val++')
|
||||
.withSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('Write-Host $val')
|
||||
.withOptionalSemicolon()
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('}')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
{
|
||||
description: 'while after closing bracket',
|
||||
input: joinAsWindowsLines(
|
||||
'try { throw "Error" } catch { Write-Host "Caught error" }',
|
||||
'while ($true) { break }',
|
||||
),
|
||||
expectedOutput: new RegexBuilder()
|
||||
.withLiteralString('try { throw "Error" } catch { Write-Host "Caught error" }')
|
||||
.withSemicolon() // Semicolon here to prevent runtime errors
|
||||
.withOptionalWhitespaceButNoNewline()
|
||||
.withLiteralString('while ($true) { break }')
|
||||
.withOptionalSemicolon()
|
||||
.buildRegex(),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,19 +1,71 @@
|
||||
import { it, expect } from 'vitest';
|
||||
import type { IPipe } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/IPipe';
|
||||
import type { Pipe } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/Pipe';
|
||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||
import { indentText } from '@/application/Common/Text/IndentText';
|
||||
|
||||
export interface IPipeTestCase {
|
||||
readonly name: string;
|
||||
export interface PipeTestScenario {
|
||||
readonly description: string;
|
||||
readonly input: string;
|
||||
readonly expectedOutput: string;
|
||||
readonly expectedOutput: RegExp | string;
|
||||
}
|
||||
|
||||
export function runPipeTests(sut: IPipe, testCases: IPipeTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
export function runPipeTests(
|
||||
pipe: Pipe,
|
||||
testScenarios: readonly PipeTestScenario[],
|
||||
) {
|
||||
testScenarios.forEach((
|
||||
{ input, description, expectedOutput: expectedInlinedOutput },
|
||||
) => {
|
||||
it(description, () => {
|
||||
// act
|
||||
const actual = sut.apply(testCase.input);
|
||||
const actualOutput = pipe.apply(input);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOutput);
|
||||
if (typeof expectedInlinedOutput === 'string') {
|
||||
expect(actualOutput).to.equal(expectedInlinedOutput);
|
||||
} else {
|
||||
expect(actualOutput).to.match(expectedInlinedOutput, formatAssertionMessage([
|
||||
'Regex did not match the output.',
|
||||
'Expected regex pattern:',
|
||||
indentText(expectedInlinedOutput.toString()),
|
||||
'Actual output:',
|
||||
indentText(actualOutput),
|
||||
'Given input:',
|
||||
indentText(input),
|
||||
]));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export class RegexBuilder {
|
||||
private rawRegex: string = '';
|
||||
|
||||
public withLiteralString(string: string): this {
|
||||
this.rawRegex += string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSomeWhitespaceButNoNewLine(): this {
|
||||
this.rawRegex += '[ \\t\\f]+';
|
||||
return this;
|
||||
}
|
||||
|
||||
public withOptionalWhitespaceButNoNewline(): this {
|
||||
this.rawRegex += '[ \\t\\f]*';
|
||||
return this;
|
||||
}
|
||||
|
||||
public withOptionalSemicolon(): this {
|
||||
this.rawRegex += ';?';
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSemicolon(): this {
|
||||
this.rawRegex += ';';
|
||||
return this;
|
||||
}
|
||||
|
||||
public buildRegex(): RegExp {
|
||||
return new RegExp(this.rawRegex, 'g');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { IPipe } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/IPipe';
|
||||
import type { Pipe } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/Pipe';
|
||||
import type { IPipeFactory } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/PipeFactory';
|
||||
|
||||
export class PipeFactoryStub implements IPipeFactory {
|
||||
private readonly pipes = new Array<IPipe>();
|
||||
private readonly pipes = new Array<Pipe>();
|
||||
|
||||
public get(pipeName: string): IPipe {
|
||||
public get(pipeName: string): Pipe {
|
||||
const result = this.pipes.find((pipe) => pipe.name === pipeName);
|
||||
if (!result) {
|
||||
throw new Error(`pipe not registered: "${pipeName}"`);
|
||||
@@ -12,12 +12,12 @@ export class PipeFactoryStub implements IPipeFactory {
|
||||
return result;
|
||||
}
|
||||
|
||||
public withPipe(pipe: IPipe) {
|
||||
public withPipe(pipe: Pipe) {
|
||||
this.pipes.push(pipe);
|
||||
return this;
|
||||
}
|
||||
|
||||
public withPipes(pipes: IPipe[]) {
|
||||
public withPipes(pipes: Pipe[]) {
|
||||
for (const pipe of pipes) {
|
||||
this.withPipe(pipe);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IPipe } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/IPipe';
|
||||
import type { Pipe } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/Pipe';
|
||||
|
||||
export class PipeStub implements IPipe {
|
||||
export class PipeStub implements Pipe {
|
||||
public name = 'pipeStub';
|
||||
|
||||
public apply(raw: string): string {
|
||||
|
||||
Reference in New Issue
Block a user