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
127 changes: 115 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,111 @@ 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 {@link CompareConfig} to be used in {@link 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 empty configuration.
*/

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
* being built.
*
* @returns {CompareConfigBuilder} A reference to the builder object to chain
*/
public default(): CompareConfigBuilder {
this._config = {
comparerFactories: [...this._config.comparerFactories, ...defaultCompareConfig.comparerFactories],
rules: { ...this._config.rules, ...defaultCompareConfig.rules },
};

return this;
}

/**
* Extends existing configuration that's built upto this point
* with the provided config.
*
* @param {CompareConfig} config - The configuration to extend with
* @returns {CompareConfigBuilder} A reference to the builder object to chain
*/
public extend(config: CompareConfig): CompareConfigBuilder {
this._config = {
comparerFactories: [...this._config.comparerFactories, ...config.comparerFactories],
rules: { ...this._config.rules, ...config.rules },
};

return this;
}

/**
* 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
* @returns {CompareConfigBuilder} A reference to the builder object to chain
*/
public addRule(ruleKey: string, result: CompareResult): CompareConfigBuilder {
this._config.rules[ruleKey] = result;

return this;
}

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

delete this._config.rules[ruleKey];

return this;
}

/**
* Add a {@link ComparerFactory} to the configuration.
*
* @param {ComparerFactory} f - A {@link ComparerFactory} that should reference the rules in the configuration
* @returns {CompareConfigBuilder} A reference to the builder object to chain
*/
public addComparerFactory(f: ComparerFactory): CompareConfigBuilder {
this._config.comparerFactories = [...this._config.comparerFactories, f];

return this;
}
}
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';

77 changes: 77 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,77 @@
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();

const actual = builder.default().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();

const actual = builder.default().extend(toExtend).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();

const actual = builder.default().addComparerFactory(() => ({})).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();

const actual = builder.default().addRule('a-new-rule', CompareResult.MAJOR).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();

const actual = builder.default().removeRule('optional-property-added').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();
expect(() => builder.default().removeRule('does-not-exist')).toThrow('ruleKey \'does-not-exist\' does not exist');
});
});
Loading