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.
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import type { LanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Analyzers/Syntax/LanguageSyntax';
|
|
import type { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
|
import { createSyntax, type SyntaxFactory } from './Syntax/SyntaxFactory';
|
|
import type { CodeLine, CodeValidationAnalyzer, InvalidCodeLine } from './CodeValidationAnalyzer';
|
|
|
|
export type DuplicateLinesAnalyzer = CodeValidationAnalyzer & {
|
|
(
|
|
...args: [
|
|
...Parameters<CodeValidationAnalyzer>,
|
|
syntaxFactory?: SyntaxFactory,
|
|
]
|
|
): ReturnType<CodeValidationAnalyzer>;
|
|
};
|
|
|
|
export const analyzeDuplicateLines: DuplicateLinesAnalyzer = (
|
|
lines: readonly CodeLine[],
|
|
language: ScriptingLanguage,
|
|
syntaxFactory = createSyntax,
|
|
) => {
|
|
const syntax = syntaxFactory(language);
|
|
return lines
|
|
.map((line): CodeLineWithDuplicateOccurrences => ({
|
|
lineNumber: line.lineNumber,
|
|
shouldBeIgnoredInAnalysis: shouldIgnoreLine(line.text, syntax),
|
|
duplicateLineNumbers: lines
|
|
.filter((other) => other.text === line.text)
|
|
.map((duplicatedLine) => duplicatedLine.lineNumber),
|
|
}))
|
|
.filter((line) => isNonIgnorableDuplicateLine(line))
|
|
.map((line): InvalidCodeLine => ({
|
|
lineNumber: line.lineNumber,
|
|
error: `Line is duplicated at line numbers ${line.duplicateLineNumbers.join(',')}.`,
|
|
}));
|
|
};
|
|
|
|
interface CodeLineWithDuplicateOccurrences {
|
|
readonly lineNumber: number;
|
|
readonly duplicateLineNumbers: readonly number[];
|
|
readonly shouldBeIgnoredInAnalysis: boolean;
|
|
}
|
|
|
|
function isNonIgnorableDuplicateLine(line: CodeLineWithDuplicateOccurrences): boolean {
|
|
return !line.shouldBeIgnoredInAnalysis && line.duplicateLineNumbers.length > 1;
|
|
}
|
|
|
|
function shouldIgnoreLine(codeLine: string, syntax: LanguageSyntax): boolean {
|
|
return isCommentLine(codeLine, syntax)
|
|
|| isLineComposedEntirelyOfCommonCodeParts(codeLine, syntax);
|
|
}
|
|
|
|
function isCommentLine(codeLine: string, syntax: LanguageSyntax): boolean {
|
|
return syntax.commentDelimiters.some(
|
|
(delimiter) => codeLine.startsWith(delimiter),
|
|
);
|
|
}
|
|
|
|
function isLineComposedEntirelyOfCommonCodeParts(
|
|
codeLine: string,
|
|
syntax: LanguageSyntax,
|
|
): boolean {
|
|
const codeLineParts = codeLine.toLowerCase().trim().split(' ');
|
|
return codeLineParts.every((part) => syntax.commonCodeParts.includes(part));
|
|
}
|