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.
185 lines
6.1 KiB
TypeScript
185 lines
6.1 KiB
TypeScript
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,
|
|
);
|
|
}
|
|
}
|