Add validation for max line length in compiler
This commit adds validation logic in compiler to check for max allowed characters per line for scripts. This allows preventing bugs caused by limitation of terminal emulators. Other supporting changes: - Rename/refactor related code for clarity and better maintainability. - Drop `I` prefix from interfaces to align with latest convention. - Refactor CodeValidator to be functional rather than object-oriented for simplicity. - Refactor syntax definition construction to be functional and be part of rule for better separation of concerns. - Refactored validation logic to use an enum-based factory pattern for improved maintainability and scalability.
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
import { describe } from 'vitest';
|
||||
import { analyzeDuplicateLines } from '@/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeDuplicateLines';
|
||||
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
|
||||
import type { CodeLine, InvalidCodeLine } from '@/application/Parser/Executable/Script/Validation/Analyzers/CodeValidationAnalyzer';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import type { SyntaxFactory } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/SyntaxFactory';
|
||||
import { SyntaxFactoryStub } from '@tests/unit/shared/Stubs/SyntaxFactoryStub';
|
||||
import { createCodeLines } from './CreateCodeLines';
|
||||
import { expectSameInvalidCodeLines } from './ExpectSameInvalidCodeLines';
|
||||
|
||||
describe('AnalyzeDuplicateLines', () => {
|
||||
describe('analyzeDuplicateLines', () => {
|
||||
it('returns no results for unique lines', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([]);
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'unique1', /* 2 */ 'unique2', /* 3 */ 'unique3', /* 4 */ 'unique4',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('identifies single duplicated line', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([1, 2, 4]);
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'duplicate', /* 2 */ 'duplicate', /* 3 */ 'unique', /* 4 */ 'duplicate',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('identifies multiple duplicated lines', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([1, 4], [2, 6]);
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'duplicate1', /* 2 */ 'duplicate2', /* 3 */ 'unique',
|
||||
/* 4 */ 'duplicate1', /* 5 */ 'unique2', /* 6 */ 'duplicate2',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
describe('syntax handling', () => {
|
||||
it('uses correct language for syntax creation', () => {
|
||||
// arrange
|
||||
const expectedLanguage = ScriptingLanguage.batchfile;
|
||||
let actualLanguage: ScriptingLanguage | undefined;
|
||||
const factory: SyntaxFactory = (language) => {
|
||||
actualLanguage = language;
|
||||
return new LanguageSyntaxStub();
|
||||
};
|
||||
const context = new TestContext()
|
||||
.withLanguage(expectedLanguage)
|
||||
.withSyntaxFactory(factory);
|
||||
// act
|
||||
context.analyze();
|
||||
// assert
|
||||
expect(actualLanguage).to.equal(expectedLanguage);
|
||||
});
|
||||
describe('common code parts', () => {
|
||||
it('ignores multiple occurrences of common code parts', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([3, 4]);
|
||||
const syntax = new LanguageSyntaxStub()
|
||||
.withCommonCodeParts('good', 'also-good');
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'good', /* 2 */ 'good', /* 3 */ 'bad', /* 4 */ 'bad',
|
||||
/* 5 */ 'good', /* 6 */ 'also-good', /* 7 */ 'also-good', /* 8 */ 'unique',
|
||||
])
|
||||
.withSyntaxFactory(() => syntax);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('ignores common code parts used in same line', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([1, 2]);
|
||||
const syntax = new LanguageSyntaxStub()
|
||||
.withCommonCodeParts('good2', 'good1');
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'bad', /* 2 */ 'bad', /* 3 */ 'good1 good2',
|
||||
/* 4 */ 'good1 good2', /* 5 */ 'good2 good1', /* 6 */ 'good2 good1',
|
||||
])
|
||||
.withSyntaxFactory(() => syntax);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('detects duplicates with common parts and unique words', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([4, 5], [8, 9]);
|
||||
const syntax = new LanguageSyntaxStub()
|
||||
.withCommonCodeParts('common-part1', 'common-part2');
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'common-part1', /* 2 */ 'common-part1', /* 3 */ 'common-part1 common-part2',
|
||||
/* 4 */ 'common-part1 unique', /* 5 */ 'common-part1 unique', /* 6 */ 'common-part2',
|
||||
/* 7 */ 'common-part2 common-part1', /* 8 */ 'unique common-part2', /* 9 */ 'unique common-part2',
|
||||
])
|
||||
.withSyntaxFactory(() => syntax);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
});
|
||||
describe('comment handling', () => {
|
||||
it('ignores lines starting with comment delimiters', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([3, 5]);
|
||||
const syntax = new LanguageSyntaxStub()
|
||||
.withCommentDelimiters('#', '//');
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ '#abc', /* 2 */ '#abc', /* 3 */ 'abc', /* 4 */ 'unique',
|
||||
/* 5 */ 'abc', /* 6 */ '//abc', /* 7 */ '//abc', /* 8 */ '//unique',
|
||||
/* 9 */ '#unique',
|
||||
])
|
||||
.withSyntaxFactory(() => syntax);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('detects duplicates when comments are not at line start', () => {
|
||||
// arrange
|
||||
const expected = createExpectedDuplicateLineErrors([1, 2], [3, 4]);
|
||||
const syntax = new LanguageSyntaxStub()
|
||||
.withCommentDelimiters('#');
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'test #comment', /* 2 */ 'test #comment', /* 3 */ 'test2 # comment',
|
||||
/* 4 */ 'test2 # comment',
|
||||
])
|
||||
.withSyntaxFactory(() => syntax);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createExpectedDuplicateLineErrors(
|
||||
...lines: readonly ReadonlyArray<number>[]
|
||||
): InvalidCodeLine[] {
|
||||
return lines.flatMap((occurrenceIndices): readonly InvalidCodeLine[] => occurrenceIndices
|
||||
.map((index): InvalidCodeLine => ({
|
||||
lineNumber: index,
|
||||
error: `Line is duplicated at line numbers ${occurrenceIndices.join(',')}.`,
|
||||
})));
|
||||
}
|
||||
|
||||
export class TestContext {
|
||||
private codeLines: readonly CodeLine[] = createCodeLines(['test-code-line']);
|
||||
|
||||
private language = ScriptingLanguage.batchfile;
|
||||
|
||||
private syntaxFactory: SyntaxFactory = new SyntaxFactoryStub().get();
|
||||
|
||||
public withLines(lines: readonly string[]): this {
|
||||
this.codeLines = createCodeLines(lines);
|
||||
return this;
|
||||
}
|
||||
|
||||
public withLanguage(language: ScriptingLanguage): this {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSyntaxFactory(syntaxFactory: SyntaxFactory): this {
|
||||
this.syntaxFactory = syntaxFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public analyze() {
|
||||
return analyzeDuplicateLines(
|
||||
this.codeLines,
|
||||
this.language,
|
||||
this.syntaxFactory,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { describe } from 'vitest';
|
||||
import { analyzeEmptyLines } from '@/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeEmptyLines';
|
||||
import type { CodeLine, InvalidCodeLine } from '@/application/Parser/Executable/Script/Validation/Analyzers/CodeValidationAnalyzer';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { createCodeLines } from './CreateCodeLines';
|
||||
import { expectSameInvalidCodeLines } from './ExpectSameInvalidCodeLines';
|
||||
|
||||
describe('AnalyzeEmptyLines', () => {
|
||||
describe('analyzeEmptyLines', () => {
|
||||
it('returns no results for non-empty lines', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [];
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'non-empty-line1', /* 2 */ 'none-empty-line2',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('identifies single empty line', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [
|
||||
{ lineNumber: 2, error: 'Empty line' },
|
||||
];
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'first line', /* 2 */ '', /* 3 */ 'third line',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('identifies multiple empty lines', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [2, 4].map((index): InvalidCodeLine => ({ lineNumber: index, error: 'Empty line' }));
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'first line', /* 2 */ '', /* 3 */ 'third line', /* 4 */ '',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('identifies lines with only spaces', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [
|
||||
{ lineNumber: 2, error: 'Empty line: "{whitespace}{whitespace}"' },
|
||||
];
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'first line', /* 2 */ ' ', /* 3 */ 'third line',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('identifies lines with only tabs', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [
|
||||
{ lineNumber: 2, error: 'Empty line: "{tab}{tab}"' },
|
||||
];
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'first line', /* 2 */ '\t\t', /* 3 */ 'third line',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
it('identifies lines with mixed spaces and tabs', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [
|
||||
{ lineNumber: 2, error: 'Empty line: "{tab}{whitespace}{tab}"' },
|
||||
{ lineNumber: 4, error: 'Empty line: "{whitespace}{tab}{whitespace}"' },
|
||||
];
|
||||
const context = new TestContext()
|
||||
.withLines([
|
||||
/* 1 */ 'first line', /* 2 */ '\t \t', /* 3 */ 'third line', /* 4 */ ' \t ',
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export class TestContext {
|
||||
private codeLines: readonly CodeLine[] = createCodeLines(['test-code-line']);
|
||||
|
||||
private language = ScriptingLanguage.batchfile;
|
||||
|
||||
public withLines(lines: readonly string[]): this {
|
||||
this.codeLines = createCodeLines(lines);
|
||||
return this;
|
||||
}
|
||||
|
||||
public withLanguage(language: ScriptingLanguage): this {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public analyze() {
|
||||
return analyzeEmptyLines(
|
||||
this.codeLines,
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { describe } from 'vitest';
|
||||
import type { CodeLine, InvalidCodeLine } from '@/application/Parser/Executable/Script/Validation/Analyzers/CodeValidationAnalyzer';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { analyzeTooLongLines } from '@/application/Parser/Executable/Script/Validation/Analyzers/AnalyzeTooLongLines';
|
||||
import { createCodeLines } from './CreateCodeLines';
|
||||
import { expectSameInvalidCodeLines } from './ExpectSameInvalidCodeLines';
|
||||
|
||||
describe('AnalyzeTooLongLines', () => {
|
||||
describe('analyzeTooLongLines', () => {
|
||||
describe('batchfile', () => {
|
||||
const MAX_BATCHFILE_LENGTH = 8191;
|
||||
|
||||
it('returns no results for lines within the maximum length', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [];
|
||||
const context = new TestContext()
|
||||
.withLanguage(ScriptingLanguage.batchfile)
|
||||
.withLines([
|
||||
'A'.repeat(MAX_BATCHFILE_LENGTH),
|
||||
'B'.repeat(8000),
|
||||
'C'.repeat(100),
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
|
||||
it('identifies a single line exceeding maximum length', () => {
|
||||
// arrange
|
||||
const expectedLength = MAX_BATCHFILE_LENGTH + 1;
|
||||
const expected: InvalidCodeLine[] = [{
|
||||
lineNumber: 2,
|
||||
error: createTooLongLineError(expectedLength, MAX_BATCHFILE_LENGTH),
|
||||
}];
|
||||
const context = new TestContext()
|
||||
.withLanguage(ScriptingLanguage.batchfile)
|
||||
.withLines([
|
||||
'A'.repeat(MAX_BATCHFILE_LENGTH),
|
||||
'B'.repeat(expectedLength),
|
||||
'C'.repeat(100),
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
|
||||
it('identifies multiple lines exceeding maximum length', () => {
|
||||
// arrange
|
||||
const expectedLength1 = MAX_BATCHFILE_LENGTH + 1;
|
||||
const expectedLength2 = MAX_BATCHFILE_LENGTH + 2;
|
||||
const expected: InvalidCodeLine[] = [
|
||||
{
|
||||
lineNumber: 1,
|
||||
error: createTooLongLineError(expectedLength1, MAX_BATCHFILE_LENGTH),
|
||||
},
|
||||
{
|
||||
lineNumber: 3,
|
||||
error: createTooLongLineError(expectedLength2, MAX_BATCHFILE_LENGTH),
|
||||
},
|
||||
];
|
||||
const context = new TestContext()
|
||||
.withLanguage(ScriptingLanguage.batchfile)
|
||||
.withLines([
|
||||
'A'.repeat(expectedLength1),
|
||||
'B'.repeat(MAX_BATCHFILE_LENGTH),
|
||||
'C'.repeat(expectedLength2),
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shellscript', () => {
|
||||
const MAX_SHELLSCRIPT_LENGTH = 1048576;
|
||||
|
||||
it('returns no results for lines within the maximum length', () => {
|
||||
// arrange
|
||||
const expected: InvalidCodeLine[] = [];
|
||||
const context = new TestContext()
|
||||
.withLanguage(ScriptingLanguage.shellscript)
|
||||
.withLines([
|
||||
'A'.repeat(MAX_SHELLSCRIPT_LENGTH),
|
||||
'B'.repeat(1000000),
|
||||
'C'.repeat(100),
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
|
||||
it('identifies a single line exceeding maximum length', () => {
|
||||
// arrange
|
||||
const expectedLength = MAX_SHELLSCRIPT_LENGTH + 1;
|
||||
const expected: InvalidCodeLine[] = [{
|
||||
lineNumber: 2,
|
||||
error: createTooLongLineError(expectedLength, MAX_SHELLSCRIPT_LENGTH),
|
||||
}];
|
||||
const context = new TestContext()
|
||||
.withLanguage(ScriptingLanguage.shellscript)
|
||||
.withLines([
|
||||
'A'.repeat(MAX_SHELLSCRIPT_LENGTH),
|
||||
'B'.repeat(expectedLength),
|
||||
'C'.repeat(100),
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
|
||||
it('identifies multiple lines exceeding maximum length', () => {
|
||||
// arrange
|
||||
const expectedLength1 = MAX_SHELLSCRIPT_LENGTH + 1;
|
||||
const expectedLength2 = MAX_SHELLSCRIPT_LENGTH + 2;
|
||||
const expected: InvalidCodeLine[] = [
|
||||
{
|
||||
lineNumber: 1,
|
||||
error: createTooLongLineError(expectedLength1, MAX_SHELLSCRIPT_LENGTH),
|
||||
},
|
||||
{
|
||||
lineNumber: 3,
|
||||
error: createTooLongLineError(expectedLength2, MAX_SHELLSCRIPT_LENGTH),
|
||||
},
|
||||
];
|
||||
const context = new TestContext()
|
||||
.withLanguage(ScriptingLanguage.shellscript)
|
||||
.withLines([
|
||||
'A'.repeat(expectedLength1),
|
||||
'B'.repeat(MAX_SHELLSCRIPT_LENGTH),
|
||||
'C'.repeat(expectedLength2),
|
||||
]);
|
||||
// act
|
||||
const actual = context.analyze();
|
||||
// assert
|
||||
expectSameInvalidCodeLines(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error for unsupported language', () => {
|
||||
// arrange
|
||||
const context = new TestContext()
|
||||
.withLanguage('unsupported' as unknown as ScriptingLanguage)
|
||||
.withLines(['A', 'B', 'C']);
|
||||
// act & assert
|
||||
expect(() => context.analyze()).to.throw('Unsupported language: unsupported');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createTooLongLineError(actualLength: number, maxAllowedLength: number): string {
|
||||
return [
|
||||
`Line is too long (${actualLength}).`,
|
||||
`It exceed maximum allowed length ${maxAllowedLength}.`,
|
||||
'This may cause bugs due to unintended trimming by operating system, shells or terminal emulators.',
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
class TestContext {
|
||||
private codeLines: readonly CodeLine[] = createCodeLines(['test-code-line']);
|
||||
|
||||
private language = ScriptingLanguage.batchfile;
|
||||
|
||||
public withLines(lines: readonly string[]): this {
|
||||
this.codeLines = createCodeLines(lines);
|
||||
return this;
|
||||
}
|
||||
|
||||
public withLanguage(language: ScriptingLanguage): this {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public analyze() {
|
||||
return analyzeTooLongLines(
|
||||
this.codeLines,
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { CodeLine } from '@/application/Parser/Executable/Script/Validation/Analyzers/CodeValidationAnalyzer';
|
||||
|
||||
export function createCodeLines(lines: readonly string[]): CodeLine[] {
|
||||
return lines.map((lineText, index): CodeLine => (
|
||||
{
|
||||
lineNumber: index + 1,
|
||||
text: lineText,
|
||||
}
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { expect } from 'vitest';
|
||||
import type { InvalidCodeLine } from '@/application/Parser/Executable/Script/Validation/Analyzers/CodeValidationAnalyzer';
|
||||
|
||||
export function expectSameInvalidCodeLines(
|
||||
expected: readonly InvalidCodeLine[],
|
||||
actual: readonly InvalidCodeLine[],
|
||||
) {
|
||||
expect(sort(expected)).to.deep.equal(sort(actual));
|
||||
}
|
||||
|
||||
function sort(lines: readonly InvalidCodeLine[]) { // To ignore order
|
||||
return Array.from(lines).sort((a, b) => a.lineNumber - b.lineNumber);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { describe } from 'vitest';
|
||||
import { BatchFileSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/BatchFileSyntax';
|
||||
import { runLanguageSyntaxTests } from './LanguageSyntaxTestRunner';
|
||||
|
||||
describe('BatchFileSyntax', () => {
|
||||
runLanguageSyntaxTests(
|
||||
() => new BatchFileSyntax(),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { LanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/LanguageSyntax';
|
||||
|
||||
export function runLanguageSyntaxTests(createSyntax: () => LanguageSyntax) {
|
||||
describe('commentDelimiters', () => {
|
||||
it('returns defined value', () => {
|
||||
// arrange
|
||||
const sut = createSyntax();
|
||||
// act
|
||||
const value = sut.commentDelimiters;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
});
|
||||
describe('commonCodeParts', () => {
|
||||
it('returns defined value', () => {
|
||||
// arrange
|
||||
const sut = createSyntax();
|
||||
// act
|
||||
const value = sut.commonCodeParts;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { describe } from 'vitest';
|
||||
import { ShellScriptSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/ShellScriptSyntax';
|
||||
import { runLanguageSyntaxTests } from './LanguageSyntaxTestRunner';
|
||||
|
||||
describe('ShellScriptSyntax', () => {
|
||||
runLanguageSyntaxTests(
|
||||
() => new ShellScriptSyntax(),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe } from 'vitest';
|
||||
import { createSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/SyntaxFactory';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ShellScriptSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/ShellScriptSyntax';
|
||||
import { BatchFileSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/BatchFileSyntax';
|
||||
import type { LanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/LanguageSyntax';
|
||||
import type { Constructible } from '@/TypeHelpers';
|
||||
|
||||
describe('SyntaxFactory', () => {
|
||||
describe('createSyntax', () => {
|
||||
it('throws given invalid language', () => {
|
||||
// arrange
|
||||
const invalidLanguage = 5 as ScriptingLanguage;
|
||||
const expectedErrorMessage = `Invalid language: "${ScriptingLanguage[invalidLanguage]}"`;
|
||||
// act
|
||||
const act = () => createSyntax(invalidLanguage);
|
||||
// assert
|
||||
expect(act).to.throw(expectedErrorMessage);
|
||||
});
|
||||
describe('creates syntax for supported languages', () => {
|
||||
const languageTestScenarios: Record<ScriptingLanguage, Constructible<LanguageSyntax>> = {
|
||||
[ScriptingLanguage.batchfile]: BatchFileSyntax,
|
||||
[ScriptingLanguage.shellscript]: ShellScriptSyntax,
|
||||
};
|
||||
Object.entries(languageTestScenarios).forEach(([key, value]) => {
|
||||
// arrange
|
||||
const scriptingLanguage = Number(key) as ScriptingLanguage;
|
||||
const expectedType = value;
|
||||
it(`gets correct type for "${ScriptingLanguage[scriptingLanguage]}" language`, () => {
|
||||
// act
|
||||
const syntax = createSyntax(scriptingLanguage);
|
||||
// assert
|
||||
expect(syntax).to.be.instanceOf(expectedType);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user