Skip to content

Commit 350a3c6

Browse files
committed
Fixes microsoft#101015: Distribute OR expressions to avoid the need for introducing paranthesis
1 parent b58df64 commit 350a3c6

2 files changed

Lines changed: 51 additions & 19 deletions

File tree

src/vs/platform/contextkey/common/contextkey.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -637,16 +637,7 @@ export class ContextKeyNotRegexExpr implements IContextKeyExpression {
637637
export class ContextKeyAndExpr implements IContextKeyExpression {
638638

639639
public static create(_expr: ReadonlyArray<ContextKeyExpression | null | undefined>): ContextKeyExpression | undefined {
640-
const expr = ContextKeyAndExpr._normalizeArr(_expr);
641-
if (expr.length === 0) {
642-
return undefined;
643-
}
644-
645-
if (expr.length === 1) {
646-
return expr[0];
647-
}
648-
649-
return new ContextKeyAndExpr(expr);
640+
return ContextKeyAndExpr._normalizeArr(_expr);
650641
}
651642

652643
public readonly type = ContextKeyExprType.And;
@@ -697,7 +688,7 @@ export class ContextKeyAndExpr implements IContextKeyExpression {
697688
return true;
698689
}
699690

700-
private static _normalizeArr(arr: ReadonlyArray<ContextKeyExpression | null | undefined>): ContextKeyExpression[] {
691+
private static _normalizeArr(arr: ReadonlyArray<ContextKeyExpression | null | undefined>): ContextKeyExpression | undefined {
701692
const expr: ContextKeyExpression[] = [];
702693
let hasTrue = false;
703694

@@ -714,29 +705,56 @@ export class ContextKeyAndExpr implements IContextKeyExpression {
714705

715706
if (e.type === ContextKeyExprType.False) {
716707
// anything && false ==> false
717-
return [ContextKeyFalseExpr.INSTANCE];
708+
return ContextKeyFalseExpr.INSTANCE;
718709
}
719710

720711
if (e.type === ContextKeyExprType.And) {
721712
expr.push(...e.expr);
722713
continue;
723714
}
724715

725-
if (e.type === ContextKeyExprType.Or) {
726-
// Not allowed, because we don't have parens!
727-
throw new Error(`It is not allowed to have an or expression here due to lack of parens! For example "a && (b||c)" is not supported, use "(a&&b) || (a&&c)" instead.`);
728-
}
729-
730716
expr.push(e);
731717
}
732718

733719
if (expr.length === 0 && hasTrue) {
734-
return [ContextKeyTrueExpr.INSTANCE];
720+
return ContextKeyTrueExpr.INSTANCE;
721+
}
722+
723+
if (expr.length === 0) {
724+
return undefined;
725+
}
726+
727+
if (expr.length === 1) {
728+
return expr[0];
735729
}
736730

737731
expr.sort(cmp);
738732

739-
return expr;
733+
// We must distribute any OR expression because we don't support parens
734+
// OR extensions will be at the end (due to sorting rules)
735+
while (expr.length > 1) {
736+
const lastElement = expr[expr.length - 1];
737+
if (lastElement.type !== ContextKeyExprType.Or) {
738+
break;
739+
}
740+
// pop the last element
741+
expr.pop();
742+
743+
// pop the second to last element
744+
const secondToLastElement = expr.pop()!;
745+
746+
// distribute `lastElement` over `secondToLastElement`
747+
const resultElement = ContextKeyOrExpr.create(
748+
lastElement.expr.map(el => ContextKeyAndExpr.create([el, secondToLastElement]))
749+
);
750+
751+
if (resultElement) {
752+
expr.push(resultElement);
753+
expr.sort(cmp);
754+
}
755+
}
756+
757+
return new ContextKeyAndExpr(expr);
740758
}
741759

742760
public serialize(): string {

src/vs/platform/contextkey/test/common/contextkey.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,18 @@ suite('ContextKeyExpr', () => {
136136
testNormalize('isLinux', isLinux ? 'true' : 'false');
137137
testNormalize('isWindows', isWindows ? 'true' : 'false');
138138
});
139+
140+
test('issue #101015: distribute OR', () => {
141+
function t(expr1: string, expr2: string, expected: string | undefined): void {
142+
const e1 = ContextKeyExpr.deserialize(expr1);
143+
const e2 = ContextKeyExpr.deserialize(expr2);
144+
const actual = ContextKeyExpr.and(e1, e2)?.serialize();
145+
assert.strictEqual(actual, expected);
146+
}
147+
t('a', 'b', 'a && b');
148+
t('a || b', 'c', 'a && c || b && c');
149+
t('a || b', 'c || d', 'a && c || b && c || a && d || b && d');
150+
t('a || b', 'c && d', 'a && c && d || b && c && d');
151+
t('a || b', 'c && d || e', 'a && e || b && e || a && c && d || b && c && d');
152+
});
139153
});

0 commit comments

Comments
 (0)