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:
undergroundwires
2024-08-27 11:32:52 +02:00
parent db090f3696
commit dc5c87376b
65 changed files with 2217 additions and 1350 deletions

View File

@@ -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,
);
}
}