refactor extra code, duplicates, complexity
- refactor array equality check and add tests - remove OperatingSystem.Unknown causing extra logic, return undefined instead - refactor enum validation to share same logic - refactor scripting language factories to share same logic - refactor too many args in runCodeAsync - refactor ScriptCode constructor to reduce complexity - fix writing useless write to member object since another property write always override it
This commit is contained in:
21
src/application/Common/Array.ts
Normal file
21
src/application/Common/Array.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// Compares to Array<T> objects for equality, ignoring order
|
||||
export function scrambledEqual<T>(array1: readonly T[], array2: readonly T[]) {
|
||||
if (!array1) { throw new Error('undefined first array'); }
|
||||
if (!array2) { throw new Error('undefined second array'); }
|
||||
const sortedArray1 = sort(array1);
|
||||
const sortedArray2 = sort(array2);
|
||||
return sequenceEqual(sortedArray1, sortedArray2);
|
||||
function sort(array: readonly T[]) {
|
||||
return array.slice().sort();
|
||||
}
|
||||
}
|
||||
|
||||
// Compares to Array<T> objects for equality in same order
|
||||
export function sequenceEqual<T>(array1: readonly T[], array2: readonly T[]) {
|
||||
if (!array1) { throw new Error('undefined first array'); }
|
||||
if (!array2) { throw new Error('undefined second array'); }
|
||||
if (array1.length !== array2.length) {
|
||||
return false;
|
||||
}
|
||||
return array1.every((val, index) => val === array2[index]);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
|
||||
type EnumType = number | string;
|
||||
type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
|
||||
export type EnumType = number | string;
|
||||
export type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
|
||||
|
||||
export interface IEnumParser<TEnum> {
|
||||
parseEnum(value: string, propertyName: string): TEnum;
|
||||
@@ -41,3 +41,14 @@ export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(
|
||||
return getEnumNames(enumVariable)
|
||||
.map((level) => enumVariable[level]) as TEnumValue[];
|
||||
}
|
||||
|
||||
export function assertInRange<T extends EnumType, TEnumValue extends EnumType>(
|
||||
value: TEnumValue,
|
||||
enumVariable: EnumVariable<T, TEnumValue>) {
|
||||
if (value === undefined) {
|
||||
throw new Error('undefined enum value');
|
||||
}
|
||||
if (!(value in enumVariable)) {
|
||||
throw new RangeError(`enum value "${value}" is out of range`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
|
||||
export interface IScriptingLanguageFactory<T> {
|
||||
create(language: ScriptingLanguage): T;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { IScriptingLanguageFactory } from './IScriptingLanguageFactory';
|
||||
import { assertInRange } from '@/application/Common/Enum';
|
||||
|
||||
type Getter<T> = () => T;
|
||||
|
||||
export abstract class ScriptingLanguageFactory<T> implements IScriptingLanguageFactory<T> {
|
||||
private readonly getters = new Map<ScriptingLanguage, Getter<T>>();
|
||||
|
||||
public create(language: ScriptingLanguage): T {
|
||||
assertInRange(language, ScriptingLanguage);
|
||||
if (!this.getters.has(language)) {
|
||||
throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
||||
}
|
||||
const getter = this.getters.get(language);
|
||||
const instance = getter();
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected registerGetter(language: ScriptingLanguage, getter: Getter<T>) {
|
||||
assertInRange(language, ScriptingLanguage);
|
||||
if (!getter) {
|
||||
throw new Error('undefined getter');
|
||||
}
|
||||
if (this.getters.has(language)) {
|
||||
throw new Error(`${ScriptingLanguage[language]} is already registered`);
|
||||
}
|
||||
this.getters.set(language, getter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { IApplication } from '@/domain/IApplication';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||
import { assertInRange } from '@/application/Common/Enum';
|
||||
|
||||
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
|
||||
|
||||
@@ -22,7 +23,7 @@ export class ApplicationContext implements IApplicationContext {
|
||||
public readonly app: IApplication,
|
||||
initialContext: OperatingSystem) {
|
||||
validateApp(app);
|
||||
validateOs(initialContext);
|
||||
assertInRange(initialContext, OperatingSystem);
|
||||
this.states = initializeStates(app);
|
||||
this.changeContext(initialContext);
|
||||
}
|
||||
@@ -50,18 +51,6 @@ function validateApp(app: IApplication) {
|
||||
}
|
||||
}
|
||||
|
||||
function validateOs(os: OperatingSystem) {
|
||||
if (os === undefined) {
|
||||
throw new Error('undefined os');
|
||||
}
|
||||
if (os === OperatingSystem.Unknown) {
|
||||
throw new Error('unknown os');
|
||||
}
|
||||
if (!(os in OperatingSystem)) {
|
||||
throw new Error(`os "${os}" is out of range`);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeStates(app: IApplication): StateMachine {
|
||||
const machine = new Map<OperatingSystem, ICategoryCollectionState>();
|
||||
for (const collection of app.collections) {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ICodeBuilder } from './ICodeBuilder';
|
||||
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
|
||||
import { BatchBuilder } from './Languages/BatchBuilder';
|
||||
import { ShellBuilder } from './Languages/ShellBuilder';
|
||||
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
|
||||
|
||||
export class CodeBuilderFactory implements ICodeBuilderFactory {
|
||||
public create(language: ScriptingLanguage): ICodeBuilder {
|
||||
switch (language) {
|
||||
case ScriptingLanguage.shellscript: return new ShellBuilder();
|
||||
case ScriptingLanguage.batchfile: return new BatchBuilder();
|
||||
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
||||
}
|
||||
export class CodeBuilderFactory extends ScriptingLanguageFactory<ICodeBuilder> implements ICodeBuilderFactory {
|
||||
constructor() {
|
||||
super();
|
||||
this.registerGetter(ScriptingLanguage.shellscript, () => new ShellBuilder());
|
||||
this.registerGetter(ScriptingLanguage.batchfile, () => new BatchBuilder());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ICodeBuilder } from './ICodeBuilder';
|
||||
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
|
||||
|
||||
export interface ICodeBuilderFactory {
|
||||
create(language: ScriptingLanguage): ICodeBuilder;
|
||||
export interface ICodeBuilderFactory extends IScriptingLanguageFactory<ICodeBuilder> {
|
||||
}
|
||||
|
||||
@@ -4,17 +4,17 @@ import { IBrowserOsDetector } from './IBrowserOsDetector';
|
||||
|
||||
export class BrowserOsDetector implements IBrowserOsDetector {
|
||||
private readonly detectors = BrowserDetectors;
|
||||
public detect(userAgent: string): OperatingSystem {
|
||||
public detect(userAgent: string): OperatingSystem | undefined {
|
||||
if (!userAgent) {
|
||||
return OperatingSystem.Unknown;
|
||||
return undefined;
|
||||
}
|
||||
for (const detector of this.detectors) {
|
||||
const os = detector.detect(userAgent);
|
||||
if (os !== OperatingSystem.Unknown) {
|
||||
if (os !== undefined) {
|
||||
return os;
|
||||
}
|
||||
}
|
||||
return OperatingSystem.Unknown;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@ export class DetectorBuilder {
|
||||
throw new Error('User agent is null or undefined');
|
||||
}
|
||||
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
|
||||
return OperatingSystem.Unknown;
|
||||
return undefined;
|
||||
}
|
||||
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
|
||||
return OperatingSystem.Unknown;
|
||||
return undefined;
|
||||
}
|
||||
return this.os;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export interface IBrowserOsDetector {
|
||||
detect(userAgent: string): OperatingSystem;
|
||||
detect(userAgent: string): OperatingSystem | undefined;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ function getProcessPlatform(variables: IEnvironmentVariables): string {
|
||||
return variables.process.platform;
|
||||
}
|
||||
|
||||
function getDesktopOsType(processPlatform: string): OperatingSystem {
|
||||
function getDesktopOsType(processPlatform: string): OperatingSystem | undefined {
|
||||
// https://nodejs.org/api/process.html#process_process_platform
|
||||
if (processPlatform === 'darwin') {
|
||||
return OperatingSystem.macOS;
|
||||
@@ -53,7 +53,7 @@ function getDesktopOsType(processPlatform: string): OperatingSystem {
|
||||
} else if (processPlatform === 'linux') {
|
||||
return OperatingSystem.Linux;
|
||||
}
|
||||
return OperatingSystem.Unknown;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isDesktop(variables: IEnvironmentVariables): boolean {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ISharedFunction } from './ISharedFunction';
|
||||
|
||||
export class SharedFunction implements ISharedFunction {
|
||||
public readonly parameters: readonly string[];
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly parameters: readonly string[],
|
||||
parameters: readonly string[],
|
||||
public readonly code: string,
|
||||
public readonly revertCode: string,
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
|
||||
|
||||
export interface ISyntaxFactory {
|
||||
create(language: ScriptingLanguage): ILanguageSyntax;
|
||||
export interface ISyntaxFactory extends IScriptingLanguageFactory<ILanguageSyntax> {
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { ISyntaxFactory } from './ISyntaxFactory';
|
||||
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
|
||||
import { BatchFileSyntax } from './BatchFileSyntax';
|
||||
import { ShellScriptSyntax } from './ShellScriptSyntax';
|
||||
import { ISyntaxFactory } from './ISyntaxFactory';
|
||||
|
||||
export class SyntaxFactory implements ISyntaxFactory {
|
||||
public create(language: ScriptingLanguage): ILanguageSyntax {
|
||||
switch (language) {
|
||||
case ScriptingLanguage.batchfile: return new BatchFileSyntax();
|
||||
case ScriptingLanguage.shellscript: return new ShellScriptSyntax();
|
||||
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
||||
}
|
||||
export class SyntaxFactory extends ScriptingLanguageFactory<ILanguageSyntax> implements ISyntaxFactory {
|
||||
constructor() {
|
||||
super();
|
||||
this.registerGetter(ScriptingLanguage.batchfile, () => new BatchFileSyntax());
|
||||
this.registerGetter(ScriptingLanguage.shellscript, () => new ShellScriptSyntax());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user