Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(analyser): add config builder to extend the analyser config #730

Merged
111 changes: 99 additions & 12 deletions packages/concerto-analysis/src/compare-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import { ComparerFactory } from './comparer';
import { comparerFactories } from './comparers';

export enum CompareResult {
NONE,
PATCH,
MINOR,
MAJOR,
ERROR,
NONE,
PATCH,
MINOR,
MAJOR,
ERROR,
}

export function compareResultToString(result: CompareResult) {
Expand All @@ -39,9 +39,9 @@ export function compareResultToString(result: CompareResult) {
}

export type CompareConfig = {
comparerFactories: ComparerFactory[];
rules: Record<string, CompareResult>;
}
comparerFactories: ComparerFactory[];
rules: Record<string, CompareResult>;
};

export const defaultCompareConfig: CompareConfig = {
comparerFactories,
Expand All @@ -63,8 +63,95 @@ export const defaultCompareConfig: CompareConfig = {
'map-key-type-changed': CompareResult.MAJOR,
'map-value-type-changed': CompareResult.MAJOR,
'scalar-extends-changed': CompareResult.MAJOR,
'scalar-validator-added' : CompareResult.MAJOR,
'scalar-validator-removed' : CompareResult.PATCH,
'scalar-validator-changed' : CompareResult.MAJOR,
}
'scalar-validator-added': CompareResult.MAJOR,
'scalar-validator-removed': CompareResult.PATCH,
'scalar-validator-changed': CompareResult.MAJOR,
},
};

const EmptyConfig: CompareConfig = {
comparerFactories: [],
rules: {},
};

export class CompareConfigBuilder {
/**
* A utility to build CompareConfig to be used in Compare class.
* A new compare config can be edited with provided functions and finally
* resulting config can be used by calling `build`.
*
* By default, it starts with an emoty configuration.
ekarademir marked this conversation as resolved.
Show resolved Hide resolved
*/

private _config: CompareConfig = EmptyConfig;

/**
* Final step of the builder
*
* @returns {CompareConfig} Resulting CompareConfig object.
*/
public build(): CompareConfig {
return {
comparerFactories: [...this._config.comparerFactories],
rules: { ...this._config.rules },
};
}

/**
* Adds default comparer configuration onto the configuration
* baing built.
ekarademir marked this conversation as resolved.
Show resolved Hide resolved
*/
public default() {
this._config = {
comparerFactories: [...this._config.comparerFactories, ...defaultCompareConfig.comparerFactories],
rules: { ...this._config.rules, ...defaultCompareConfig.rules },
};
}

/**
* Extends existing configuration tha't built up to this point
* with the provided config.
*
* @param {CompareConfig} config - The configuration to extend with
*/
public extend(config: CompareConfig) {
this._config = {
comparerFactories: [...this._config.comparerFactories, ...config.comparerFactories],
rules: { ...this._config.rules, ...config.rules },
};
}

/**
* Adds a comparison outcome rule to the configuration
*
* @param {string} ruleKey - A key that is referenced from one of the comparer factories
* @param {CompareResult} result - A version diff outcome based on this rule
*/
public addRule(ruleKey: string, result: CompareResult) {
this._config.rules[ruleKey] = result;
}

/**
* Removes a comparison outcome rule from the configuration
*
* @param {string} ruleKey - A key that is referenced from one of the comparer factories
* @throws {ReferenceError}
* Thrown if the `ruleKey` does not exist in the configuration
*/
public removeRule(ruleKey: string) {
if (!this._config.rules[ruleKey]) {
throw new ReferenceError(`ruleKey '${ruleKey}' does not exist`);
}

delete this._config.rules[ruleKey];
}

/**
* Add a {@link ComparerFactory} to the configuration.
*
* @param {ComparerFactory} f - A {@link ComparerFactory} that should reference the rules in the configuration
*/
public addComparerFactory(f: ComparerFactory) {
this._config.comparerFactories = [...this._config.comparerFactories, f];
}
}
2 changes: 1 addition & 1 deletion packages/concerto-analysis/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
*/

export { Compare } from './compare';
export { CompareConfig, CompareResult, compareResultToString } from './compare-config';
export { CompareConfig, CompareResult, CompareConfigBuilder, compareResultToString } from './compare-config';
export { CompareFinding, CompareResults } from './compare-results';

87 changes: 87 additions & 0 deletions packages/concerto-analysis/test/unit/compare-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { CompareConfig, CompareConfigBuilder, CompareResult } from '../../src/compare-config';

describe('CompareConfigBuilder', () => {
it('Should start with empty config', () => {
const builder = new CompareConfigBuilder();

const actual = builder.build();

expect(actual.comparerFactories.length).toEqual(0);
expect(Object.keys(actual.rules).length).toEqual(0);
});

it('Should add default config with `default`', () => {
const builder = new CompareConfigBuilder();
builder.default();

const actual = builder.build();

expect(actual.comparerFactories.length).toEqual(11);
expect(Object.keys(actual.rules).length).toEqual(20);
expect(actual.rules['class-declaration-added']).toEqual(CompareResult.MINOR);
expect(actual.rules['optional-property-added']).toEqual(CompareResult.PATCH);
expect(actual.rules['map-value-type-changed']).toEqual(CompareResult.MAJOR);
});

it('Should extend config', () => {
const newRules = {
'a-new-rule': CompareResult.MAJOR
};
const toExtend: CompareConfig = {
comparerFactories: [
() => ({}),
],
rules: newRules
};
const builder = new CompareConfigBuilder();
builder.default();
builder.extend(toExtend);

const actual = builder.build();

expect(actual.comparerFactories.length).toEqual(12);
expect(Object.keys(actual.rules).length).toEqual(21);
expect(actual.rules['a-new-rule']).toEqual(CompareResult.MAJOR);
});

it('Should add a new comparer factory', () => {
const builder = new CompareConfigBuilder();
builder.default();
builder.addComparerFactory(() => ({}));

const actual = builder.build();

expect(actual.comparerFactories.length).toEqual(12);
expect(Object.keys(actual.rules).length).toEqual(20);
});

it('Should add a new rule', () => {
const builder = new CompareConfigBuilder();
builder.default();
builder.addRule('a-new-rule', CompareResult.MAJOR);

const actual = builder.build();

expect(actual.comparerFactories.length).toEqual(11);
expect(Object.keys(actual.rules).length).toEqual(21);
expect(actual.rules['a-new-rule']).toEqual(CompareResult.MAJOR);
});

it('Should remove an existing rule', () => {
const builder = new CompareConfigBuilder();
builder.default();
builder.removeRule('optional-property-added');

const actual = builder.build();

expect(actual.comparerFactories.length).toEqual(11);
expect(Object.keys(actual.rules).length).toEqual(19);
expect(actual.rules['optional-property-added']).toBeFalsy();
});

it('Should throw while removing a rule that does not exist', () => {
const builder = new CompareConfigBuilder();
builder.default();
expect(() => builder.removeRule('does-not-exist')).toThrow('ruleKey \'does-not-exist\' does not exist');
});
});
Loading