- Unify test data for nonexistence of an object/string and collection. - Introduce more test through adding missing test data to existing tests. - Improve logic for checking absence of values to match tests. - Add missing tests for absent value validation. - Update documentation to include shared test functionality.
85 lines
2.7 KiB
TypeScript
85 lines
2.7 KiB
TypeScript
import { IScriptCode } from './IScriptCode';
|
|
|
|
export class ScriptCode implements IScriptCode {
|
|
constructor(
|
|
public readonly execute: string,
|
|
public readonly revert: string,
|
|
syntax: ILanguageSyntax,
|
|
) {
|
|
if (!syntax) { throw new Error('missing syntax'); }
|
|
validateCode(execute, syntax);
|
|
validateRevertCode(revert, execute, syntax);
|
|
}
|
|
}
|
|
|
|
export interface ILanguageSyntax {
|
|
readonly commentDelimiters: string[];
|
|
readonly commonCodeParts: string[];
|
|
}
|
|
|
|
function validateRevertCode(revertCode: string, execute: string, syntax: ILanguageSyntax) {
|
|
if (!revertCode) {
|
|
return;
|
|
}
|
|
try {
|
|
validateCode(revertCode, syntax);
|
|
if (execute === revertCode) {
|
|
throw new Error('Code itself and its reverting code cannot be the same');
|
|
}
|
|
} catch (err) {
|
|
throw Error(`(revert): ${err.message}`);
|
|
}
|
|
}
|
|
|
|
function validateCode(code: string, syntax: ILanguageSyntax): void {
|
|
if (!code || code.length === 0) {
|
|
throw new Error('missing code');
|
|
}
|
|
ensureNoEmptyLines(code);
|
|
ensureCodeHasUniqueLines(code, syntax);
|
|
}
|
|
|
|
function ensureNoEmptyLines(code: string): void {
|
|
const lines = code.split(/\r\n|\r|\n/);
|
|
if (lines.some((line) => line.trim().length === 0)) {
|
|
throw Error(`Script has empty lines:\n${lines.map((part, index) => `\n (${index}) ${part || '❌'}`).join('')}`);
|
|
}
|
|
}
|
|
|
|
function ensureCodeHasUniqueLines(code: string, syntax: ILanguageSyntax): void {
|
|
const allLines = code.split(/\r\n|\r|\n/);
|
|
const checkedLines = allLines.filter((line) => !shouldIgnoreLine(line, syntax));
|
|
if (checkedLines.length === 0) {
|
|
return;
|
|
}
|
|
const duplicateLines = checkedLines.filter((e, i, a) => a.indexOf(e) !== i);
|
|
if (duplicateLines.length !== 0) {
|
|
throw Error(`Duplicates detected in script:\n${printDuplicatedLines(allLines)}`);
|
|
}
|
|
}
|
|
|
|
function printDuplicatedLines(allLines: string[]) {
|
|
return allLines
|
|
.map((line, index) => {
|
|
const occurrenceIndices = allLines
|
|
.map((e, i) => (e === line ? i : ''))
|
|
.filter(String);
|
|
const isDuplicate = occurrenceIndices.length > 1;
|
|
const indicator = isDuplicate ? `❌ (${occurrenceIndices.join(',')})\t` : '✅ ';
|
|
return `${indicator}[${index}] ${line}`;
|
|
})
|
|
.join('\n');
|
|
}
|
|
|
|
function shouldIgnoreLine(codeLine: string, syntax: ILanguageSyntax): boolean {
|
|
const lowerCaseCodeLine = codeLine.toLowerCase();
|
|
const isCommentLine = () => syntax.commentDelimiters.some(
|
|
(delimiter) => lowerCaseCodeLine.startsWith(delimiter),
|
|
);
|
|
const consistsOfFrequentCommands = () => {
|
|
const trimmed = lowerCaseCodeLine.trim().split(' ');
|
|
return trimmed.every((part) => syntax.commonCodeParts.includes(part));
|
|
};
|
|
return isCommentLine() || consistsOfFrequentCommands();
|
|
}
|