Files
privacy.sexy/src/domain/ScriptCode.ts
undergroundwires 44d79e2c9a Add more and unify tests for absent object cases
- 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.
2022-01-21 22:34:11 +01:00

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();
}