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,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user