diff --git a/docs/api/cautious-journey.md b/docs/api/cautious-journey.md
index b7a95ff..a8f5418 100644
--- a/docs/api/cautious-journey.md
+++ b/docs/api/cautious-journey.md
@@ -15,7 +15,7 @@
| Function | Description |
| --- | --- |
-| [resolveLabels(options)](./cautious-journey.resolvelabels.md) | Resolve the desired set of labels, given a starting set and the flags/states to be applied. |
+| [resolveProject(options)](./cautious-journey.resolveproject.md) | |
| [syncIssueLabels(options)](./cautious-journey.syncissuelabels.md) | goes through and resolves each issue in the project. if there are changes and no errors, then updates the issue. |
| [syncProjectLabels(options)](./cautious-journey.syncprojectlabels.md) | |
diff --git a/docs/api/cautious-journey.resolvelabels.md b/docs/api/cautious-journey.resolveproject.md
similarity index 58%
rename from docs/api/cautious-journey.resolvelabels.md
rename to docs/api/cautious-journey.resolveproject.md
index 9a0820c..4c5f737 100644
--- a/docs/api/cautious-journey.resolvelabels.md
+++ b/docs/api/cautious-journey.resolveproject.md
@@ -1,15 +1,13 @@
-[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [resolveLabels](./cautious-journey.resolvelabels.md)
+[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [resolveProject](./cautious-journey.resolveproject.md)
-## resolveLabels() function
-
-Resolve the desired set of labels, given a starting set and the flags/states to be applied.
+## resolveProject() function
Signature:
```typescript
-export declare function resolveLabels(options: ResolveInput): ResolveResult;
+export declare function resolveProject(options: ResolveInput): ResolveResult;
```
## Parameters
diff --git a/src/index.ts b/src/index.ts
index 7d6c919..770f64f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,7 +4,7 @@ export { ChangeSet, FlagLabel, StateLabel, StateValue } from './labels';
export { Remote, RemoteOptions } from './remote';
export { GithubRemote } from './remote/github';
export { GitlabRemote } from './remote/gitlab';
-export { ResolveInput, ResolveResult, resolveLabels } from './resolve';
+export { ResolveInput, ResolveResult, resolveProject } from './resolve';
export { syncIssueLabels, SyncOptions, syncProjectLabels } from './sync';
const STATUS_ERROR = 1;
diff --git a/src/main.ts b/src/main.ts
index 550e60e..9a3be92 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -16,7 +16,7 @@ export { FlagLabel, StateLabel } from './labels';
export { Remote, RemoteOptions } from './remote';
export { GithubRemote } from './remote/github';
export { GitlabRemote } from './remote/gitlab';
-export { resolveLabels } from './resolve';
+export { resolveProject } from './resolve';
export { syncIssueLabels, syncProjectLabels } from './sync';
const ARGS_START = 2;
diff --git a/src/resolve.ts b/src/resolve.ts
index 1dc05f4..9a419b5 100644
--- a/src/resolve.ts
+++ b/src/resolve.ts
@@ -1,6 +1,6 @@
import { doesExist } from '@apextoaster/js-utils';
-import { BaseLabel, FlagLabel, getValueName, prioritySort, StateLabel } from './labels';
+import { BaseLabel, FlagLabel, getValueName, prioritySort, StateLabel, StateValue } from './labels';
import { defaultUntil } from './utils';
/**
@@ -61,131 +61,142 @@ export interface ResolveResult {
* Resolve the desired set of labels, given a starting set and the flags/states to be
* applied.
*/
-/* eslint-disable-next-line sonarjs/cognitive-complexity */
-export function resolveLabels(options: ResolveInput): ResolveResult {
- const activeLabels = new Set(options.labels);
- const changes: Array = [];
- const errors: Array = [];
+function resolveBaseLabel(label: BaseLabel, anticipatedResult: ResolveResult, activeLabels: Set) {
+ if (activeLabels.has(label.name) === false) {
+ return true;
+ }
+
+ for (const requiredLabel of label.requires) {
+ if (!activeLabels.has(requiredLabel.name)) {
+ if (activeLabels.delete(label.name)) {
+ anticipatedResult.changes.push({
+ cause: requiredLabel.name,
+ effect: ChangeVerb.REQUIRED,
+ label: label.name,
+ });
+ }
- function checkLabelRules(label: BaseLabel) {
- if (activeLabels.has(label.name) === false) {
return true;
}
-
- for (const requiredLabel of label.requires) {
- if (!activeLabels.has(requiredLabel.name)) {
- if (activeLabels.delete(label.name)) {
- changes.push({
- cause: requiredLabel.name,
- effect: ChangeVerb.REQUIRED,
- label: label.name,
- });
- }
-
- return true;
- }
- }
-
- for (const addedLabel of label.adds) {
- // Set.add does not return a boolean, unlike the other methods
- if (!activeLabels.has(addedLabel.name)) {
- activeLabels.add(addedLabel.name);
- changes.push({
- cause: label.name,
- effect: ChangeVerb.CREATED,
- label: addedLabel.name,
- });
- }
- }
-
- for (const removedLabel of label.removes) {
- if (activeLabels.delete(removedLabel.name)) {
- changes.push({
- cause: label.name,
- effect: ChangeVerb.REMOVED,
- label: removedLabel.name,
- });
- }
- }
-
- return false;
}
+ for (const addedLabel of label.adds) {
+ // Set.add does not return a boolean, unlike the other methods
+ if (!activeLabels.has(addedLabel.name)) {
+ activeLabels.add(addedLabel.name);
+ anticipatedResult.changes.push({
+ cause: label.name,
+ effect: ChangeVerb.CREATED,
+ label: addedLabel.name,
+ });
+ }
+ }
+
+ for (const removedLabel of label.removes) {
+ if (activeLabels.delete(removedLabel.name)) {
+ anticipatedResult.changes.push({
+ cause: label.name,
+ effect: ChangeVerb.REMOVED,
+ label: removedLabel.name,
+ });
+ }
+ }
+
+ return false;
+}
+
+function resolveBecomes(label: BaseLabel, anticipatedResult: ResolveResult, activeLabels: Set, value: StateValue): boolean {
+ for (const become of value.becomes) {
+ const matches = become.matches.every((l) => activeLabels.has(l.name));
+
+ if (matches) {
+ resolveBaseLabel({
+ ...label,
+ adds: become.adds,
+ removes: [...become.matches, ...become.removes],
+ requires: [],
+ }, anticipatedResult, activeLabels);
+
+ if (activeLabels.delete(name)) {
+ anticipatedResult.changes.push({
+ cause: name,
+ effect: ChangeVerb.REMOVED,
+ label: name,
+ });
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Need to ensure that there is only 1 active value for the state
+ * If no, remove any lower priority active values for the state
+ * Need to run the normal (add, remove) rules
+ * Need to run the becomes rules
+ */
+function resolveState(state: StateLabel, anticipatedResult: ResolveResult, activeLabels: Set) {
+ let activeValue;
+
+ const sortedValues = prioritySort(state.values);
+ for (const value of sortedValues) {
+ const name = getValueName(state, value);
+ if (!activeLabels.has(name)) {
+ continue;
+ }
+
+ if (doesExist(activeValue)) { // there is already an active value
+ if (activeLabels.delete(name)) {
+ anticipatedResult.changes.push({
+ cause: name,
+ effect: ChangeVerb.CONFLICTED,
+ label: name,
+ });
+ }
+
+ continue;
+ }
+
+ const combinedValue: BaseLabel = {
+ adds: [...state.adds, ...value.adds],
+ name,
+ priority: defaultUntil(value.priority, state.priority, 0),
+ removes: [...state.removes, ...value.removes],
+ requires: [...state.requires, ...value.requires],
+ };
+
+ if (resolveBaseLabel(combinedValue, anticipatedResult, activeLabels)) {
+ continue;
+ }
+
+ if (resolveBecomes(combinedValue, anticipatedResult, activeLabels, value)) {
+ continue;
+ }
+
+ activeValue = name;
+ }
+}
+
+export function resolveProject(options: ResolveInput): ResolveResult {
+ const result: ResolveResult = {
+ changes: [],
+ errors: [],
+ labels: [],
+ };
+ const activeLabels = new Set(options.labels);
+
const sortedFlags = prioritySort(options.flags);
for (const flag of sortedFlags) {
- checkLabelRules(flag);
+ resolveBaseLabel(flag, result, activeLabels);
}
const sortedStates = prioritySort(options.states);
for (const state of sortedStates) {
- let activeValue;
-
- const sortedValues = prioritySort(state.values);
- for (const value of sortedValues) {
- const name = getValueName(state, value);
- if (activeLabels.has(name)) {
- if (doesExist(activeValue)) {
- if (activeLabels.delete(name)) {
- changes.push({
- cause: name,
- effect: ChangeVerb.CONFLICTED,
- label: name,
- });
- }
-
- continue;
- }
-
- const combinedValue: BaseLabel = {
- adds: [...state.adds, ...value.adds],
- name,
- priority: defaultUntil(value.priority, state.priority, 0),
- removes: [...state.removes, ...value.removes],
- requires: [...state.requires, ...value.requires],
- };
-
- if (checkLabelRules(combinedValue)) {
- continue;
- }
-
- // TODO: flatten this bit and remove the mutable boolean
- let removed = false;
- for (const become of value.becomes) {
- const matches = become.matches.every((l) => activeLabels.has(l.name));
-
- if (matches) {
- checkLabelRules({
- ...combinedValue,
- adds: become.adds,
- removes: [...become.matches, ...become.removes],
- requires: [],
- });
-
- if (activeLabels.delete(name)) {
- changes.push({
- cause: name,
- effect: ChangeVerb.REMOVED,
- label: name,
- });
- removed = true;
- }
-
- break;
- }
-
- if (removed) {
- continue;
- }
-
- activeValue = name;
- }
- }
- }
+ resolveState(state, result, activeLabels);
}
- return {
- changes,
- errors,
- labels: Array.from(activeLabels),
- };
+ result.labels = Array.from(activeLabels);
+
+ return result;
}
diff --git a/src/sync.ts b/src/sync.ts
index 05f10c9..fdf0edc 100644
--- a/src/sync.ts
+++ b/src/sync.ts
@@ -5,7 +5,7 @@ import { prng } from 'seedrandom';
import { ProjectConfig } from './config';
import { getLabelColor, getLabelNames, getValueName } from './labels';
import { LabelUpdate, Remote } from './remote';
-import { resolveLabels } from './resolve';
+import { resolveProject } from './resolve';
import { compareItems, defaultTo, defaultUntil } from './utils';
export interface SyncOptions {
@@ -28,7 +28,7 @@ export async function syncIssueLabels(options: SyncOptions): Promise {
for (const issue of issues) {
logger.info({ issue }, 'project issue');
- const { changes, errors, labels } = resolveLabels({
+ const { changes, errors, labels } = resolveProject({
flags: project.flags,
labels: issue.labels,
states: project.states,
diff --git a/test/TestResolve.ts b/test/TestResolve.ts
index 05d946b..ce92f49 100644
--- a/test/TestResolve.ts
+++ b/test/TestResolve.ts
@@ -1,6 +1,6 @@
import { expect } from 'chai';
-import { resolveLabels } from '../src/resolve';
+import { resolveProject } from '../src/resolve';
import { TEST_CASES } from './resolve/cases';
const TEST_LABELS = ['foo', 'bar'];
@@ -8,7 +8,7 @@ const TEST_LABELS = ['foo', 'bar'];
describe('resolve labels', () => {
describe('with empty rule set', () => {
it('should return the existing labels', () => {
- const result = resolveLabels({
+ const result = resolveProject({
flags: [],
labels: TEST_LABELS,
states: [],
@@ -18,7 +18,7 @@ describe('resolve labels', () => {
});
it('should not make any changes', () => {
- const result = resolveLabels({
+ const result = resolveProject({
flags: [],
labels: TEST_LABELS,
states: [],
@@ -32,7 +32,7 @@ describe('resolve labels', () => {
describe('resolver test cases', () => {
for (const test of TEST_CASES) {
it(`should resolve ${test.name}`, () => {
- const actualResult = resolveLabels(test.input);
+ const actualResult = resolveProject(test.input);
expect(actualResult).to.deep.equal(test.result);
});
}
diff --git a/test/resolve/TestResolveIssueLabels.ts b/test/resolve/TestResolveIssueLabels.ts
index 0599fe9..f101337 100644
--- a/test/resolve/TestResolveIssueLabels.ts
+++ b/test/resolve/TestResolveIssueLabels.ts
@@ -1,13 +1,13 @@
import { expect } from 'chai';
-import { resolveLabels } from '../../src/resolve';
+import { resolveProject } from '../../src/resolve';
const TEST_LABELS = ['foo', 'bar'];
describe('resolve labels', () => {
describe('flags with unfulfilled requires rule', () => {
it('should be removed when required label is missing', () => {
- const result = resolveLabels({
+ const result = resolveProject({
flags: [{
adds: [],
name: 'gayle',
@@ -27,7 +27,7 @@ describe('resolve labels', () => {
describe('flags with fulfilled requires rule', () => {
it('should make no changes', () => {
- const result = resolveLabels({
+ const result = resolveProject({
flags: [{
adds: [],
name: 'gayle',
@@ -47,7 +47,7 @@ describe('resolve labels', () => {
describe('flags with add rules', () => {
it('should add the labels', () => {
- const result = resolveLabels({
+ const result = resolveProject({
flags: [{
adds: [{
name: 'linda',
@@ -67,7 +67,7 @@ describe('resolve labels', () => {
describe('flags with remove rules', () => {
it('should remove labels', () => {
- const result = resolveLabels({
+ const result = resolveProject({
flags: [{
adds: [],
name: 'bob',