Path: blob/main/src/vs/platform/contextkey/common/contextkey.ts
5251 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CharCode } from '../../../base/common/charCode.js';6import { Event } from '../../../base/common/event.js';7import { isChrome, isEdge, isFirefox, isLinux, isMacintosh, isSafari, isWeb, isWindows } from '../../../base/common/platform.js';8import { isFalsyOrWhitespace } from '../../../base/common/strings.js';9import { Scanner, LexingError, Token, TokenType } from './scanner.js';10import { createDecorator } from '../../instantiation/common/instantiation.js';11import { localize } from '../../../nls.js';12import { IDisposable } from '../../../base/common/lifecycle.js';13import { illegalArgument } from '../../../base/common/errors.js';1415const CONSTANT_VALUES = new Map<string, boolean>();16CONSTANT_VALUES.set('false', false);17CONSTANT_VALUES.set('true', true);18CONSTANT_VALUES.set('isMac', isMacintosh);19CONSTANT_VALUES.set('isLinux', isLinux);20CONSTANT_VALUES.set('isWindows', isWindows);21CONSTANT_VALUES.set('isWeb', isWeb);22CONSTANT_VALUES.set('isMacNative', isMacintosh && !isWeb);23CONSTANT_VALUES.set('isEdge', isEdge);24CONSTANT_VALUES.set('isFirefox', isFirefox);25CONSTANT_VALUES.set('isChrome', isChrome);26CONSTANT_VALUES.set('isSafari', isSafari);2728/** allow register constant context keys that are known only after startup; requires running `substituteConstants` on the context key - https://github.com/microsoft/vscode/issues/174218#issuecomment-1437972127 */29export function setConstant(key: string, value: boolean) {30if (CONSTANT_VALUES.get(key) !== undefined) { throw illegalArgument('contextkey.setConstant(k, v) invoked with already set constant `k`'); }3132CONSTANT_VALUES.set(key, value);33}3435const hasOwnProperty = Object.prototype.hasOwnProperty;3637export const enum ContextKeyExprType {38False = 0,39True = 1,40Defined = 2,41Not = 3,42Equals = 4,43NotEquals = 5,44And = 6,45Regex = 7,46NotRegex = 8,47Or = 9,48In = 10,49NotIn = 11,50Greater = 12,51GreaterEquals = 13,52Smaller = 14,53SmallerEquals = 15,54}5556export interface IContextKeyExprMapper {57mapDefined(key: string): ContextKeyExpression;58mapNot(key: string): ContextKeyExpression;59mapEquals(key: string, value: any): ContextKeyExpression;60mapNotEquals(key: string, value: any): ContextKeyExpression;61mapGreater(key: string, value: any): ContextKeyExpression;62mapGreaterEquals(key: string, value: any): ContextKeyExpression;63mapSmaller(key: string, value: any): ContextKeyExpression;64mapSmallerEquals(key: string, value: any): ContextKeyExpression;65mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr;66mapIn(key: string, valueKey: string): ContextKeyInExpr;67mapNotIn(key: string, valueKey: string): ContextKeyNotInExpr;68}6970export interface IContextKeyExpression {71cmp(other: ContextKeyExpression): number;72equals(other: ContextKeyExpression): boolean;73substituteConstants(): ContextKeyExpression | undefined;74evaluate(context: IContext): boolean;75serialize(): string;76keys(): string[];77map(mapFnc: IContextKeyExprMapper): ContextKeyExpression;78negate(): ContextKeyExpression;7980}8182export type ContextKeyExpression = (83ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr84| ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr85| ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr86| ContextKeyNotInExpr | ContextKeyGreaterExpr | ContextKeyGreaterEqualsExpr87| ContextKeySmallerExpr | ContextKeySmallerEqualsExpr88);899091/*9293Syntax grammar:9495```ebnf9697expression ::= or9899or ::= and { '||' and }*100101and ::= term { '&&' term }*102103term ::=104| '!' (KEY | true | false | parenthesized)105| primary106107primary ::=108| 'true'109| 'false'110| parenthesized111| KEY '=~' REGEX112| KEY [ ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'not' 'in' | 'in') value ]113114parenthesized ::=115| '(' expression ')'116117value ::=118| 'true'119| 'false'120| 'in' // we support `in` as a value because there's an extension that uses it, ie "when": "languageId == in"121| VALUE // matched by the same regex as KEY; consider putting the value in single quotes if it's a string (e.g., with spaces)122| SINGLE_QUOTED_STR123| EMPTY_STR // this allows "when": "foo == " which's used by existing extensions124125```126*/127128export type ParserConfig = {129/**130* with this option enabled, the parser can recover from regex parsing errors, e.g., unescaped slashes: `/src//` is accepted as `/src\//` would be131*/132regexParsingWithErrorRecovery: boolean;133};134135const defaultConfig: ParserConfig = {136regexParsingWithErrorRecovery: true137};138139export type ParsingError = {140message: string;141offset: number;142lexeme: string;143additionalInfo?: string;144};145146const errorEmptyString = localize('contextkey.parser.error.emptyString', "Empty context key expression");147const hintEmptyString = localize('contextkey.parser.error.emptyString.hint', "Did you forget to write an expression? You can also put 'false' or 'true' to always evaluate to false or true, respectively.");148const errorNoInAfterNot = localize('contextkey.parser.error.noInAfterNot', "'in' after 'not'.");149const errorClosingParenthesis = localize('contextkey.parser.error.closingParenthesis', "closing parenthesis ')'");150const errorUnexpectedToken = localize('contextkey.parser.error.unexpectedToken', "Unexpected token");151const hintUnexpectedToken = localize('contextkey.parser.error.unexpectedToken.hint', "Did you forget to put && or || before the token?");152const errorUnexpectedEOF = localize('contextkey.parser.error.unexpectedEOF', "Unexpected end of expression");153const hintUnexpectedEOF = localize('contextkey.parser.error.unexpectedEOF.hint', "Did you forget to put a context key?");154155/**156* A parser for context key expressions.157*158* Example:159* ```ts160* const parser = new Parser();161* const expr = parser.parse('foo == "bar" && baz == true');162*163* if (expr === undefined) {164* // there were lexing or parsing errors165* // process lexing errors with `parser.lexingErrors`166* // process parsing errors with `parser.parsingErrors`167* } else {168* // expr is a valid expression169* }170* ```171*/172export class Parser {173// Note: this doesn't produce an exact syntax tree but a normalized one174// ContextKeyExpression's that we use as AST nodes do not expose constructors that do not normalize175176private static _parseError = new Error();177178// lifetime note: `_scanner` lives as long as the parser does, i.e., is not reset between calls to `parse`179private readonly _scanner = new Scanner();180181// lifetime note: `_tokens`, `_current`, and `_parsingErrors` must be reset between calls to `parse`182private _tokens: Token[] = [];183private _current = 0; // invariant: 0 <= this._current < this._tokens.length ; any incrementation of this value must first call `_isAtEnd`184private _parsingErrors: ParsingError[] = [];185186get lexingErrors(): Readonly<LexingError[]> {187return this._scanner.errors;188}189190get parsingErrors(): Readonly<ParsingError[]> {191return this._parsingErrors;192}193194constructor(private readonly _config: ParserConfig = defaultConfig) {195}196197/**198* Parse a context key expression.199*200* @param input the expression to parse201* @returns the parsed expression or `undefined` if there's an error - call `lexingErrors` and `parsingErrors` to see the errors202*/203parse(input: string): ContextKeyExpression | undefined {204205if (input === '') {206this._parsingErrors.push({ message: errorEmptyString, offset: 0, lexeme: '', additionalInfo: hintEmptyString });207return undefined;208}209210this._tokens = this._scanner.reset(input).scan();211// @ulugbekna: we do not stop parsing if there are lexing errors to be able to reconstruct regexes with unescaped slashes; TODO@ulugbekna: make this respect config option for recovery212213this._current = 0;214this._parsingErrors = [];215216try {217const expr = this._expr();218if (!this._isAtEnd()) {219const peek = this._peek();220const additionalInfo = peek.type === TokenType.Str ? hintUnexpectedToken : undefined;221this._parsingErrors.push({ message: errorUnexpectedToken, offset: peek.offset, lexeme: Scanner.getLexeme(peek), additionalInfo });222throw Parser._parseError;223}224return expr;225} catch (e) {226if (!(e === Parser._parseError)) {227throw e;228}229return undefined;230}231}232233private _expr(): ContextKeyExpression | undefined {234return this._or();235}236237private _or(): ContextKeyExpression | undefined {238const expr = [this._and()];239240while (this._matchOne(TokenType.Or)) {241const right = this._and();242expr.push(right);243}244245return expr.length === 1 ? expr[0] : ContextKeyExpr.or(...expr);246}247248private _and(): ContextKeyExpression | undefined {249const expr = [this._term()];250251while (this._matchOne(TokenType.And)) {252const right = this._term();253expr.push(right);254}255256return expr.length === 1 ? expr[0] : ContextKeyExpr.and(...expr);257}258259private _term(): ContextKeyExpression | undefined {260if (this._matchOne(TokenType.Neg)) {261const peek = this._peek();262switch (peek.type) {263case TokenType.True:264this._advance();265return ContextKeyFalseExpr.INSTANCE;266case TokenType.False:267this._advance();268return ContextKeyTrueExpr.INSTANCE;269case TokenType.LParen: {270this._advance();271const expr = this._expr();272this._consume(TokenType.RParen, errorClosingParenthesis);273return expr?.negate();274}275case TokenType.Str:276this._advance();277return ContextKeyNotExpr.create(peek.lexeme);278default:279throw this._errExpectedButGot(`KEY | true | false | '(' expression ')'`, peek);280}281}282return this._primary();283}284285private _primary(): ContextKeyExpression | undefined {286287const peek = this._peek();288switch (peek.type) {289case TokenType.True:290this._advance();291return ContextKeyExpr.true();292293case TokenType.False:294this._advance();295return ContextKeyExpr.false();296297case TokenType.LParen: {298this._advance();299const expr = this._expr();300this._consume(TokenType.RParen, errorClosingParenthesis);301return expr;302}303304case TokenType.Str: {305// KEY306const key = peek.lexeme;307this._advance();308309// =~ regex310if (this._matchOne(TokenType.RegexOp)) {311312// @ulugbekna: we need to reconstruct the regex from the tokens because some extensions use unescaped slashes in regexes313const expr = this._peek();314315if (!this._config.regexParsingWithErrorRecovery) {316this._advance();317if (expr.type !== TokenType.RegexStr) {318throw this._errExpectedButGot(`REGEX`, expr);319}320const regexLexeme = expr.lexeme;321const closingSlashIndex = regexLexeme.lastIndexOf('/');322const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : this._removeFlagsGY(regexLexeme.substring(closingSlashIndex + 1));323let regexp: RegExp | null;324try {325regexp = new RegExp(regexLexeme.substring(1, closingSlashIndex), flags);326} catch (e) {327throw this._errExpectedButGot(`REGEX`, expr);328}329return ContextKeyRegexExpr.create(key, regexp);330}331332switch (expr.type) {333case TokenType.RegexStr:334case TokenType.Error: { // also handle an ErrorToken in case of smth such as /(/file)/335const lexemeReconstruction = [expr.lexeme]; // /REGEX/ or /REGEX/FLAGS336this._advance();337338let followingToken = this._peek();339let parenBalance = 0;340for (let i = 0; i < expr.lexeme.length; i++) {341if (expr.lexeme.charCodeAt(i) === CharCode.OpenParen) {342parenBalance++;343} else if (expr.lexeme.charCodeAt(i) === CharCode.CloseParen) {344parenBalance--;345}346}347348while (!this._isAtEnd() && followingToken.type !== TokenType.And && followingToken.type !== TokenType.Or) {349switch (followingToken.type) {350case TokenType.LParen:351parenBalance++;352break;353case TokenType.RParen:354parenBalance--;355break;356case TokenType.RegexStr:357case TokenType.QuotedStr:358for (let i = 0; i < followingToken.lexeme.length; i++) {359if (followingToken.lexeme.charCodeAt(i) === CharCode.OpenParen) {360parenBalance++;361} else if (expr.lexeme.charCodeAt(i) === CharCode.CloseParen) {362parenBalance--;363}364}365}366if (parenBalance < 0) {367break;368}369lexemeReconstruction.push(Scanner.getLexeme(followingToken));370this._advance();371followingToken = this._peek();372}373374const regexLexeme = lexemeReconstruction.join('');375const closingSlashIndex = regexLexeme.lastIndexOf('/');376const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : this._removeFlagsGY(regexLexeme.substring(closingSlashIndex + 1));377let regexp: RegExp | null;378try {379regexp = new RegExp(regexLexeme.substring(1, closingSlashIndex), flags);380} catch (e) {381throw this._errExpectedButGot(`REGEX`, expr);382}383return ContextKeyExpr.regex(key, regexp);384}385386case TokenType.QuotedStr: {387const serializedValue = expr.lexeme;388this._advance();389// replicate old regex parsing behavior390391let regex: RegExp | null = null;392393if (!isFalsyOrWhitespace(serializedValue)) {394const start = serializedValue.indexOf('/');395const end = serializedValue.lastIndexOf('/');396if (start !== end && start >= 0) {397398const value = serializedValue.slice(start + 1, end);399const caseIgnoreFlag = serializedValue[end + 1] === 'i' ? 'i' : '';400try {401regex = new RegExp(value, caseIgnoreFlag);402} catch (_e) {403throw this._errExpectedButGot(`REGEX`, expr);404}405}406}407408if (regex === null) {409throw this._errExpectedButGot('REGEX', expr);410}411412return ContextKeyRegexExpr.create(key, regex);413}414415default:416throw this._errExpectedButGot('REGEX', this._peek());417}418}419420// [ 'not' 'in' value ]421if (this._matchOne(TokenType.Not)) {422this._consume(TokenType.In, errorNoInAfterNot);423const right = this._value();424return ContextKeyExpr.notIn(key, right);425}426427// [ ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'in') value ]428const maybeOp = this._peek().type;429switch (maybeOp) {430case TokenType.Eq: {431this._advance();432433const right = this._value();434if (this._previous().type === TokenType.QuotedStr) { // to preserve old parser behavior: "foo == 'true'" is preserved as "foo == 'true'", but "foo == true" is optimized as "foo"435return ContextKeyExpr.equals(key, right);436}437switch (right) {438case 'true':439return ContextKeyExpr.has(key);440case 'false':441return ContextKeyExpr.not(key);442default:443return ContextKeyExpr.equals(key, right);444}445}446447case TokenType.NotEq: {448this._advance();449450const right = this._value();451if (this._previous().type === TokenType.QuotedStr) { // same as above with "foo != 'true'"452return ContextKeyExpr.notEquals(key, right);453}454switch (right) {455case 'true':456return ContextKeyExpr.not(key);457case 'false':458return ContextKeyExpr.has(key);459default:460return ContextKeyExpr.notEquals(key, right);461}462}463// TODO: ContextKeyExpr.smaller(key, right) accepts only `number` as `right` AND during eval of this node, we just eval to `false` if `right` is not a number464// consequently, package.json linter should _warn_ the user if they're passing undesired things to ops465case TokenType.Lt:466this._advance();467return ContextKeySmallerExpr.create(key, this._value());468469case TokenType.LtEq:470this._advance();471return ContextKeySmallerEqualsExpr.create(key, this._value());472473case TokenType.Gt:474this._advance();475return ContextKeyGreaterExpr.create(key, this._value());476477case TokenType.GtEq:478this._advance();479return ContextKeyGreaterEqualsExpr.create(key, this._value());480481case TokenType.In:482this._advance();483return ContextKeyExpr.in(key, this._value());484485default:486return ContextKeyExpr.has(key);487}488}489490case TokenType.EOF:491this._parsingErrors.push({ message: errorUnexpectedEOF, offset: peek.offset, lexeme: '', additionalInfo: hintUnexpectedEOF });492throw Parser._parseError;493494default:495throw this._errExpectedButGot(`true | false | KEY \n\t| KEY '=~' REGEX \n\t| KEY ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | 'not' 'in') value`, this._peek());496497}498}499500private _value(): string {501const token = this._peek();502switch (token.type) {503case TokenType.Str:504case TokenType.QuotedStr:505this._advance();506return token.lexeme;507case TokenType.True:508this._advance();509return 'true';510case TokenType.False:511this._advance();512return 'false';513case TokenType.In: // we support `in` as a value, e.g., "when": "languageId == in" - exists in existing extensions514this._advance();515return 'in';516default:517// this allows "when": "foo == " which's used by existing extensions518// we do not call `_advance` on purpose - we don't want to eat unintended tokens519return '';520}521}522523private _flagsGYRe = /g|y/g;524private _removeFlagsGY(flags: string): string {525return flags.replaceAll(this._flagsGYRe, '');526}527528// careful: this can throw if current token is the initial one (ie index = 0)529private _previous() {530return this._tokens[this._current - 1];531}532533private _matchOne(token: TokenType) {534if (this._check(token)) {535this._advance();536return true;537}538539return false;540}541542private _advance() {543if (!this._isAtEnd()) {544this._current++;545}546return this._previous();547}548549private _consume(type: TokenType, message: string) {550if (this._check(type)) {551return this._advance();552}553554throw this._errExpectedButGot(message, this._peek());555}556557private _errExpectedButGot(expected: string, got: Token, additionalInfo?: string) {558const message = localize('contextkey.parser.error.expectedButGot', "Expected: {0}\nReceived: '{1}'.", expected, Scanner.getLexeme(got));559const offset = got.offset;560const lexeme = Scanner.getLexeme(got);561this._parsingErrors.push({ message, offset, lexeme, additionalInfo });562return Parser._parseError;563}564565private _check(type: TokenType) {566return this._peek().type === type;567}568569private _peek() {570return this._tokens[this._current];571}572573private _isAtEnd() {574return this._peek().type === TokenType.EOF;575}576}577578export abstract class ContextKeyExpr {579580public static false(): ContextKeyExpression {581return ContextKeyFalseExpr.INSTANCE;582}583public static true(): ContextKeyExpression {584return ContextKeyTrueExpr.INSTANCE;585}586public static has(key: string): ContextKeyExpression {587return ContextKeyDefinedExpr.create(key);588}589public static equals(key: string, value: any): ContextKeyExpression {590return ContextKeyEqualsExpr.create(key, value);591}592public static notEquals(key: string, value: any): ContextKeyExpression {593return ContextKeyNotEqualsExpr.create(key, value);594}595public static regex(key: string, value: RegExp): ContextKeyExpression {596return ContextKeyRegexExpr.create(key, value);597}598public static in(key: string, value: string): ContextKeyExpression {599return ContextKeyInExpr.create(key, value);600}601public static notIn(key: string, value: string): ContextKeyExpression {602return ContextKeyNotInExpr.create(key, value);603}604public static not(key: string): ContextKeyExpression {605return ContextKeyNotExpr.create(key);606}607public static and(...expr: Array<ContextKeyExpression | undefined | null>): ContextKeyExpression | undefined {608return ContextKeyAndExpr.create(expr, null, true);609}610public static or(...expr: Array<ContextKeyExpression | undefined | null>): ContextKeyExpression | undefined {611return ContextKeyOrExpr.create(expr, null, true);612}613public static greater(key: string, value: number): ContextKeyExpression {614return ContextKeyGreaterExpr.create(key, value);615}616public static greaterEquals(key: string, value: number): ContextKeyExpression {617return ContextKeyGreaterEqualsExpr.create(key, value);618}619public static smaller(key: string, value: number): ContextKeyExpression {620return ContextKeySmallerExpr.create(key, value);621}622public static smallerEquals(key: string, value: number): ContextKeyExpression {623return ContextKeySmallerEqualsExpr.create(key, value);624}625626private static _parser = new Parser({ regexParsingWithErrorRecovery: false });627public static deserialize(serialized: string | null | undefined): ContextKeyExpression | undefined {628if (serialized === undefined || serialized === null) { // an empty string needs to be handled by the parser to get a corresponding parsing error reported629return undefined;630}631632const expr = this._parser.parse(serialized);633return expr;634}635636}637638639export function validateWhenClauses(whenClauses: string[]): any {640641const parser = new Parser({ regexParsingWithErrorRecovery: false }); // we run with no recovery to guide users to use correct regexes642643return whenClauses.map(whenClause => {644parser.parse(whenClause);645646if (parser.lexingErrors.length > 0) {647return parser.lexingErrors.map((se: LexingError) => ({648errorMessage: se.additionalInfo ?649localize('contextkey.scanner.errorForLinterWithHint', "Unexpected token. Hint: {0}", se.additionalInfo) :650localize('contextkey.scanner.errorForLinter', "Unexpected token."),651offset: se.offset,652length: se.lexeme.length,653}));654} else if (parser.parsingErrors.length > 0) {655return parser.parsingErrors.map((pe: ParsingError) => ({656errorMessage: pe.additionalInfo ? `${pe.message}. ${pe.additionalInfo}` : pe.message,657offset: pe.offset,658length: pe.lexeme.length,659}));660} else {661return [];662}663});664}665666export function expressionsAreEqualWithConstantSubstitution(a: ContextKeyExpression | null | undefined, b: ContextKeyExpression | null | undefined): boolean {667const aExpr = a ? a.substituteConstants() : undefined;668const bExpr = b ? b.substituteConstants() : undefined;669if (!aExpr && !bExpr) {670return true;671}672if (!aExpr || !bExpr) {673return false;674}675return aExpr.equals(bExpr);676}677678function cmp(a: ContextKeyExpression, b: ContextKeyExpression): number {679return a.cmp(b);680}681682export class ContextKeyFalseExpr implements IContextKeyExpression {683public static INSTANCE = new ContextKeyFalseExpr();684685public readonly type = ContextKeyExprType.False;686687protected constructor() {688}689690public cmp(other: ContextKeyExpression): number {691return this.type - other.type;692}693694public equals(other: ContextKeyExpression): boolean {695return (other.type === this.type);696}697698public substituteConstants(): ContextKeyExpression | undefined {699return this;700}701702public evaluate(context: IContext): boolean {703return false;704}705706public serialize(): string {707return 'false';708}709710public keys(): string[] {711return [];712}713714public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {715return this;716}717718public negate(): ContextKeyExpression {719return ContextKeyTrueExpr.INSTANCE;720}721}722723export class ContextKeyTrueExpr implements IContextKeyExpression {724public static INSTANCE = new ContextKeyTrueExpr();725726public readonly type = ContextKeyExprType.True;727728protected constructor() {729}730731public cmp(other: ContextKeyExpression): number {732return this.type - other.type;733}734735public equals(other: ContextKeyExpression): boolean {736return (other.type === this.type);737}738739public substituteConstants(): ContextKeyExpression | undefined {740return this;741}742743public evaluate(context: IContext): boolean {744return true;745}746747public serialize(): string {748return 'true';749}750751public keys(): string[] {752return [];753}754755public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {756return this;757}758759public negate(): ContextKeyExpression {760return ContextKeyFalseExpr.INSTANCE;761}762}763764export class ContextKeyDefinedExpr implements IContextKeyExpression {765public static create(key: string, negated: ContextKeyExpression | null = null): ContextKeyExpression {766const constantValue = CONSTANT_VALUES.get(key);767if (typeof constantValue === 'boolean') {768return constantValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE;769}770return new ContextKeyDefinedExpr(key, negated);771}772773public readonly type = ContextKeyExprType.Defined;774775protected constructor(776readonly key: string,777private negated: ContextKeyExpression | null778) {779}780781public cmp(other: ContextKeyExpression): number {782if (other.type !== this.type) {783return this.type - other.type;784}785return cmp1(this.key, other.key);786}787788public equals(other: ContextKeyExpression): boolean {789if (other.type === this.type) {790return (this.key === other.key);791}792return false;793}794795public substituteConstants(): ContextKeyExpression | undefined {796const constantValue = CONSTANT_VALUES.get(this.key);797if (typeof constantValue === 'boolean') {798return constantValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE;799}800return this;801}802803public evaluate(context: IContext): boolean {804return (!!context.getValue(this.key));805}806807public serialize(): string {808return this.key;809}810811public keys(): string[] {812return [this.key];813}814815public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {816return mapFnc.mapDefined(this.key);817}818819public negate(): ContextKeyExpression {820if (!this.negated) {821this.negated = ContextKeyNotExpr.create(this.key, this);822}823return this.negated;824}825}826827export class ContextKeyEqualsExpr implements IContextKeyExpression {828829public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {830if (typeof value === 'boolean') {831return (value ? ContextKeyDefinedExpr.create(key, negated) : ContextKeyNotExpr.create(key, negated));832}833const constantValue = CONSTANT_VALUES.get(key);834if (typeof constantValue === 'boolean') {835const trueValue = constantValue ? 'true' : 'false';836return (value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE);837}838return new ContextKeyEqualsExpr(key, value, negated);839}840841public readonly type = ContextKeyExprType.Equals;842843private constructor(844private readonly key: string,845private readonly value: any,846private negated: ContextKeyExpression | null847) {848}849850public cmp(other: ContextKeyExpression): number {851if (other.type !== this.type) {852return this.type - other.type;853}854return cmp2(this.key, this.value, other.key, other.value);855}856857public equals(other: ContextKeyExpression): boolean {858if (other.type === this.type) {859return (this.key === other.key && this.value === other.value);860}861return false;862}863864public substituteConstants(): ContextKeyExpression | undefined {865const constantValue = CONSTANT_VALUES.get(this.key);866if (typeof constantValue === 'boolean') {867const trueValue = constantValue ? 'true' : 'false';868return (this.value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE);869}870return this;871}872873public evaluate(context: IContext): boolean {874// Intentional ==875// eslint-disable-next-line eqeqeq876return (context.getValue(this.key) == this.value);877}878879public serialize(): string {880return `${this.key} == '${this.value}'`;881}882883public keys(): string[] {884return [this.key];885}886887public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {888return mapFnc.mapEquals(this.key, this.value);889}890891public negate(): ContextKeyExpression {892if (!this.negated) {893this.negated = ContextKeyNotEqualsExpr.create(this.key, this.value, this);894}895return this.negated;896}897}898899export class ContextKeyInExpr implements IContextKeyExpression {900901public static create(key: string, valueKey: string): ContextKeyInExpr {902return new ContextKeyInExpr(key, valueKey);903}904905public readonly type = ContextKeyExprType.In;906private negated: ContextKeyExpression | null = null;907908private constructor(909private readonly key: string,910private readonly valueKey: string,911) {912}913914public cmp(other: ContextKeyExpression): number {915if (other.type !== this.type) {916return this.type - other.type;917}918return cmp2(this.key, this.valueKey, other.key, other.valueKey);919}920921public equals(other: ContextKeyExpression): boolean {922if (other.type === this.type) {923return (this.key === other.key && this.valueKey === other.valueKey);924}925return false;926}927928public substituteConstants(): ContextKeyExpression | undefined {929return this;930}931932public evaluate(context: IContext): boolean {933const source = context.getValue(this.valueKey);934935const item = context.getValue(this.key);936937if (Array.isArray(source)) {938// eslint-disable-next-line local/code-no-any-casts939return source.includes(item as any);940}941942if (typeof item === 'string' && typeof source === 'object' && source !== null) {943return hasOwnProperty.call(source, item);944}945return false;946}947948public serialize(): string {949return `${this.key} in '${this.valueKey}'`;950}951952public keys(): string[] {953return [this.key, this.valueKey];954}955956public map(mapFnc: IContextKeyExprMapper): ContextKeyInExpr {957return mapFnc.mapIn(this.key, this.valueKey);958}959960public negate(): ContextKeyExpression {961if (!this.negated) {962this.negated = ContextKeyNotInExpr.create(this.key, this.valueKey);963}964return this.negated;965}966}967968export class ContextKeyNotInExpr implements IContextKeyExpression {969970public static create(key: string, valueKey: string): ContextKeyNotInExpr {971return new ContextKeyNotInExpr(key, valueKey);972}973974public readonly type = ContextKeyExprType.NotIn;975976private readonly _negated: ContextKeyInExpr;977978private constructor(979private readonly key: string,980private readonly valueKey: string,981) {982this._negated = ContextKeyInExpr.create(key, valueKey);983}984985public cmp(other: ContextKeyExpression): number {986if (other.type !== this.type) {987return this.type - other.type;988}989return this._negated.cmp(other._negated);990}991992public equals(other: ContextKeyExpression): boolean {993if (other.type === this.type) {994return this._negated.equals(other._negated);995}996return false;997}998999public substituteConstants(): ContextKeyExpression | undefined {1000return this;1001}10021003public evaluate(context: IContext): boolean {1004return !this._negated.evaluate(context);1005}10061007public serialize(): string {1008return `${this.key} not in '${this.valueKey}'`;1009}10101011public keys(): string[] {1012return this._negated.keys();1013}10141015public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1016return mapFnc.mapNotIn(this.key, this.valueKey);1017}10181019public negate(): ContextKeyExpression {1020return this._negated;1021}1022}10231024export class ContextKeyNotEqualsExpr implements IContextKeyExpression {10251026public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {1027if (typeof value === 'boolean') {1028if (value) {1029return ContextKeyNotExpr.create(key, negated);1030}1031return ContextKeyDefinedExpr.create(key, negated);1032}1033const constantValue = CONSTANT_VALUES.get(key);1034if (typeof constantValue === 'boolean') {1035const falseValue = constantValue ? 'true' : 'false';1036return (value === falseValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE);1037}1038return new ContextKeyNotEqualsExpr(key, value, negated);1039}10401041public readonly type = ContextKeyExprType.NotEquals;10421043private constructor(1044private readonly key: string,1045private readonly value: any,1046private negated: ContextKeyExpression | null1047) {1048}10491050public cmp(other: ContextKeyExpression): number {1051if (other.type !== this.type) {1052return this.type - other.type;1053}1054return cmp2(this.key, this.value, other.key, other.value);1055}10561057public equals(other: ContextKeyExpression): boolean {1058if (other.type === this.type) {1059return (this.key === other.key && this.value === other.value);1060}1061return false;1062}10631064public substituteConstants(): ContextKeyExpression | undefined {1065const constantValue = CONSTANT_VALUES.get(this.key);1066if (typeof constantValue === 'boolean') {1067const falseValue = constantValue ? 'true' : 'false';1068return (this.value === falseValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE);1069}1070return this;1071}10721073public evaluate(context: IContext): boolean {1074// Intentional !=1075// eslint-disable-next-line eqeqeq1076return (context.getValue(this.key) != this.value);1077}10781079public serialize(): string {1080return `${this.key} != '${this.value}'`;1081}10821083public keys(): string[] {1084return [this.key];1085}10861087public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1088return mapFnc.mapNotEquals(this.key, this.value);1089}10901091public negate(): ContextKeyExpression {1092if (!this.negated) {1093this.negated = ContextKeyEqualsExpr.create(this.key, this.value, this);1094}1095return this.negated;1096}1097}10981099export class ContextKeyNotExpr implements IContextKeyExpression {11001101public static create(key: string, negated: ContextKeyExpression | null = null): ContextKeyExpression {1102const constantValue = CONSTANT_VALUES.get(key);1103if (typeof constantValue === 'boolean') {1104return (constantValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE);1105}1106return new ContextKeyNotExpr(key, negated);1107}11081109public readonly type = ContextKeyExprType.Not;11101111private constructor(1112private readonly key: string,1113private negated: ContextKeyExpression | null1114) {1115}11161117public cmp(other: ContextKeyExpression): number {1118if (other.type !== this.type) {1119return this.type - other.type;1120}1121return cmp1(this.key, other.key);1122}11231124public equals(other: ContextKeyExpression): boolean {1125if (other.type === this.type) {1126return (this.key === other.key);1127}1128return false;1129}11301131public substituteConstants(): ContextKeyExpression | undefined {1132const constantValue = CONSTANT_VALUES.get(this.key);1133if (typeof constantValue === 'boolean') {1134return (constantValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE);1135}1136return this;1137}11381139public evaluate(context: IContext): boolean {1140return (!context.getValue(this.key));1141}11421143public serialize(): string {1144return `!${this.key}`;1145}11461147public keys(): string[] {1148return [this.key];1149}11501151public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1152return mapFnc.mapNot(this.key);1153}11541155public negate(): ContextKeyExpression {1156if (!this.negated) {1157this.negated = ContextKeyDefinedExpr.create(this.key, this);1158}1159return this.negated;1160}1161}11621163function withFloatOrStr<T extends ContextKeyExpression>(value: any, callback: (value: number | string) => T): T | ContextKeyFalseExpr {1164if (typeof value === 'string') {1165const n = parseFloat(value);1166if (!isNaN(n)) {1167value = n;1168}1169}1170if (typeof value === 'string' || typeof value === 'number') {1171return callback(value);1172}1173return ContextKeyFalseExpr.INSTANCE;1174}11751176export class ContextKeyGreaterExpr implements IContextKeyExpression {11771178public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {1179return withFloatOrStr(_value, (value) => new ContextKeyGreaterExpr(key, value, negated));1180}11811182public readonly type = ContextKeyExprType.Greater;11831184private constructor(1185private readonly key: string,1186private readonly value: number | string,1187private negated: ContextKeyExpression | null1188) { }11891190public cmp(other: ContextKeyExpression): number {1191if (other.type !== this.type) {1192return this.type - other.type;1193}1194return cmp2(this.key, this.value, other.key, other.value);1195}11961197public equals(other: ContextKeyExpression): boolean {1198if (other.type === this.type) {1199return (this.key === other.key && this.value === other.value);1200}1201return false;1202}12031204public substituteConstants(): ContextKeyExpression | undefined {1205return this;1206}12071208public evaluate(context: IContext): boolean {1209if (typeof this.value === 'string') {1210return false;1211}1212return (parseFloat(context.getValue<any>(this.key)) > this.value);1213}12141215public serialize(): string {1216return `${this.key} > ${this.value}`;1217}12181219public keys(): string[] {1220return [this.key];1221}12221223public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1224return mapFnc.mapGreater(this.key, this.value);1225}12261227public negate(): ContextKeyExpression {1228if (!this.negated) {1229this.negated = ContextKeySmallerEqualsExpr.create(this.key, this.value, this);1230}1231return this.negated;1232}1233}12341235export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression {12361237public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {1238return withFloatOrStr(_value, (value) => new ContextKeyGreaterEqualsExpr(key, value, negated));1239}12401241public readonly type = ContextKeyExprType.GreaterEquals;12421243private constructor(1244private readonly key: string,1245private readonly value: number | string,1246private negated: ContextKeyExpression | null1247) { }12481249public cmp(other: ContextKeyExpression): number {1250if (other.type !== this.type) {1251return this.type - other.type;1252}1253return cmp2(this.key, this.value, other.key, other.value);1254}12551256public equals(other: ContextKeyExpression): boolean {1257if (other.type === this.type) {1258return (this.key === other.key && this.value === other.value);1259}1260return false;1261}12621263public substituteConstants(): ContextKeyExpression | undefined {1264return this;1265}12661267public evaluate(context: IContext): boolean {1268if (typeof this.value === 'string') {1269return false;1270}1271return (parseFloat(context.getValue<any>(this.key)) >= this.value);1272}12731274public serialize(): string {1275return `${this.key} >= ${this.value}`;1276}12771278public keys(): string[] {1279return [this.key];1280}12811282public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1283return mapFnc.mapGreaterEquals(this.key, this.value);1284}12851286public negate(): ContextKeyExpression {1287if (!this.negated) {1288this.negated = ContextKeySmallerExpr.create(this.key, this.value, this);1289}1290return this.negated;1291}1292}12931294export class ContextKeySmallerExpr implements IContextKeyExpression {12951296public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {1297return withFloatOrStr(_value, (value) => new ContextKeySmallerExpr(key, value, negated));1298}12991300public readonly type = ContextKeyExprType.Smaller;13011302private constructor(1303private readonly key: string,1304private readonly value: number | string,1305private negated: ContextKeyExpression | null1306) {1307}13081309public cmp(other: ContextKeyExpression): number {1310if (other.type !== this.type) {1311return this.type - other.type;1312}1313return cmp2(this.key, this.value, other.key, other.value);1314}13151316public equals(other: ContextKeyExpression): boolean {1317if (other.type === this.type) {1318return (this.key === other.key && this.value === other.value);1319}1320return false;1321}13221323public substituteConstants(): ContextKeyExpression | undefined {1324return this;1325}13261327public evaluate(context: IContext): boolean {1328if (typeof this.value === 'string') {1329return false;1330}1331return (parseFloat(context.getValue<any>(this.key)) < this.value);1332}13331334public serialize(): string {1335return `${this.key} < ${this.value}`;1336}13371338public keys(): string[] {1339return [this.key];1340}13411342public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1343return mapFnc.mapSmaller(this.key, this.value);1344}13451346public negate(): ContextKeyExpression {1347if (!this.negated) {1348this.negated = ContextKeyGreaterEqualsExpr.create(this.key, this.value, this);1349}1350return this.negated;1351}1352}13531354export class ContextKeySmallerEqualsExpr implements IContextKeyExpression {13551356public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {1357return withFloatOrStr(_value, (value) => new ContextKeySmallerEqualsExpr(key, value, negated));1358}13591360public readonly type = ContextKeyExprType.SmallerEquals;13611362private constructor(1363private readonly key: string,1364private readonly value: number | string,1365private negated: ContextKeyExpression | null1366) {1367}13681369public cmp(other: ContextKeyExpression): number {1370if (other.type !== this.type) {1371return this.type - other.type;1372}1373return cmp2(this.key, this.value, other.key, other.value);1374}13751376public equals(other: ContextKeyExpression): boolean {1377if (other.type === this.type) {1378return (this.key === other.key && this.value === other.value);1379}1380return false;1381}13821383public substituteConstants(): ContextKeyExpression | undefined {1384return this;1385}13861387public evaluate(context: IContext): boolean {1388if (typeof this.value === 'string') {1389return false;1390}1391return (parseFloat(context.getValue<any>(this.key)) <= this.value);1392}13931394public serialize(): string {1395return `${this.key} <= ${this.value}`;1396}13971398public keys(): string[] {1399return [this.key];1400}14011402public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1403return mapFnc.mapSmallerEquals(this.key, this.value);1404}14051406public negate(): ContextKeyExpression {1407if (!this.negated) {1408this.negated = ContextKeyGreaterExpr.create(this.key, this.value, this);1409}1410return this.negated;1411}1412}14131414export class ContextKeyRegexExpr implements IContextKeyExpression {14151416public static create(key: string, regexp: RegExp | null): ContextKeyRegexExpr {1417return new ContextKeyRegexExpr(key, regexp);1418}14191420public readonly type = ContextKeyExprType.Regex;1421private negated: ContextKeyExpression | null = null;14221423private constructor(1424private readonly key: string,1425private readonly regexp: RegExp | null1426) {1427//1428}14291430public cmp(other: ContextKeyExpression): number {1431if (other.type !== this.type) {1432return this.type - other.type;1433}1434if (this.key < other.key) {1435return -1;1436}1437if (this.key > other.key) {1438return 1;1439}1440const thisSource = this.regexp ? this.regexp.source : '';1441const otherSource = other.regexp ? other.regexp.source : '';1442if (thisSource < otherSource) {1443return -1;1444}1445if (thisSource > otherSource) {1446return 1;1447}1448return 0;1449}14501451public equals(other: ContextKeyExpression): boolean {1452if (other.type === this.type) {1453const thisSource = this.regexp ? this.regexp.source : '';1454const otherSource = other.regexp ? other.regexp.source : '';1455return (this.key === other.key && thisSource === otherSource);1456}1457return false;1458}14591460public substituteConstants(): ContextKeyExpression | undefined {1461return this;1462}14631464public evaluate(context: IContext): boolean {1465const value = context.getValue<any>(this.key);1466return this.regexp ? this.regexp.test(value) : false;1467}14681469public serialize(): string {1470const value = this.regexp1471? `/${this.regexp.source}/${this.regexp.flags}`1472: '/invalid/';1473return `${this.key} =~ ${value}`;1474}14751476public keys(): string[] {1477return [this.key];1478}14791480public map(mapFnc: IContextKeyExprMapper): ContextKeyRegexExpr {1481return mapFnc.mapRegex(this.key, this.regexp);1482}14831484public negate(): ContextKeyExpression {1485if (!this.negated) {1486this.negated = ContextKeyNotRegexExpr.create(this);1487}1488return this.negated;1489}1490}14911492export class ContextKeyNotRegexExpr implements IContextKeyExpression {14931494public static create(actual: ContextKeyRegexExpr): ContextKeyExpression {1495return new ContextKeyNotRegexExpr(actual);1496}14971498public readonly type = ContextKeyExprType.NotRegex;14991500private constructor(private readonly _actual: ContextKeyRegexExpr) {1501//1502}15031504public cmp(other: ContextKeyExpression): number {1505if (other.type !== this.type) {1506return this.type - other.type;1507}1508return this._actual.cmp(other._actual);1509}15101511public equals(other: ContextKeyExpression): boolean {1512if (other.type === this.type) {1513return this._actual.equals(other._actual);1514}1515return false;1516}15171518public substituteConstants(): ContextKeyExpression | undefined {1519return this;1520}15211522public evaluate(context: IContext): boolean {1523return !this._actual.evaluate(context);1524}15251526public serialize(): string {1527return `!(${this._actual.serialize()})`;1528}15291530public keys(): string[] {1531return this._actual.keys();1532}15331534public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1535return new ContextKeyNotRegexExpr(this._actual.map(mapFnc));1536}15371538public negate(): ContextKeyExpression {1539return this._actual;1540}1541}15421543/**1544* @returns the same instance if nothing changed.1545*/1546function eliminateConstantsInArray(arr: ContextKeyExpression[]): (ContextKeyExpression | undefined)[] {1547// Allocate array only if there is a difference1548let newArr: (ContextKeyExpression | undefined)[] | null = null;1549for (let i = 0, len = arr.length; i < len; i++) {1550const newExpr = arr[i].substituteConstants();15511552if (arr[i] !== newExpr) {1553// something has changed!15541555// allocate array on first difference1556if (newArr === null) {1557newArr = [];1558for (let j = 0; j < i; j++) {1559newArr[j] = arr[j];1560}1561}1562}15631564if (newArr !== null) {1565newArr[i] = newExpr;1566}1567}15681569if (newArr === null) {1570return arr;1571}1572return newArr;1573}15741575export class ContextKeyAndExpr implements IContextKeyExpression {15761577public static create(_expr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {1578return ContextKeyAndExpr._normalizeArr(_expr, negated, extraRedundantCheck);1579}15801581public readonly type = ContextKeyExprType.And;15821583private constructor(1584public readonly expr: ContextKeyExpression[],1585private negated: ContextKeyExpression | null1586) {1587}15881589public cmp(other: ContextKeyExpression): number {1590if (other.type !== this.type) {1591return this.type - other.type;1592}1593if (this.expr.length < other.expr.length) {1594return -1;1595}1596if (this.expr.length > other.expr.length) {1597return 1;1598}1599for (let i = 0, len = this.expr.length; i < len; i++) {1600const r = cmp(this.expr[i], other.expr[i]);1601if (r !== 0) {1602return r;1603}1604}1605return 0;1606}16071608public equals(other: ContextKeyExpression): boolean {1609if (other.type === this.type) {1610if (this.expr.length !== other.expr.length) {1611return false;1612}1613for (let i = 0, len = this.expr.length; i < len; i++) {1614if (!this.expr[i].equals(other.expr[i])) {1615return false;1616}1617}1618return true;1619}1620return false;1621}16221623public substituteConstants(): ContextKeyExpression | undefined {1624const exprArr = eliminateConstantsInArray(this.expr);1625if (exprArr === this.expr) {1626// no change1627return this;1628}1629return ContextKeyAndExpr.create(exprArr, this.negated, false);1630}16311632public evaluate(context: IContext): boolean {1633for (let i = 0, len = this.expr.length; i < len; i++) {1634if (!this.expr[i].evaluate(context)) {1635return false;1636}1637}1638return true;1639}16401641private static _normalizeArr(arr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {1642const expr: ContextKeyExpression[] = [];1643let hasTrue = false;16441645for (const e of arr) {1646if (!e) {1647continue;1648}16491650if (e.type === ContextKeyExprType.True) {1651// anything && true ==> anything1652hasTrue = true;1653continue;1654}16551656if (e.type === ContextKeyExprType.False) {1657// anything && false ==> false1658return ContextKeyFalseExpr.INSTANCE;1659}16601661if (e.type === ContextKeyExprType.And) {1662expr.push(...e.expr);1663continue;1664}16651666expr.push(e);1667}16681669if (expr.length === 0 && hasTrue) {1670return ContextKeyTrueExpr.INSTANCE;1671}16721673if (expr.length === 0) {1674return undefined;1675}16761677if (expr.length === 1) {1678return expr[0];1679}16801681expr.sort(cmp);16821683// eliminate duplicate terms1684for (let i = 1; i < expr.length; i++) {1685if (expr[i - 1].equals(expr[i])) {1686expr.splice(i, 1);1687i--;1688}1689}16901691if (expr.length === 1) {1692return expr[0];1693}16941695// We must distribute any OR expression because we don't support parens1696// OR extensions will be at the end (due to sorting rules)1697while (expr.length > 1) {1698const lastElement = expr[expr.length - 1];1699if (lastElement.type !== ContextKeyExprType.Or) {1700break;1701}1702// pop the last element1703expr.pop();17041705// pop the second to last element1706const secondToLastElement = expr.pop()!;17071708const isFinished = (expr.length === 0);17091710// distribute `lastElement` over `secondToLastElement`1711const resultElement = ContextKeyOrExpr.create(1712lastElement.expr.map(el => ContextKeyAndExpr.create([el, secondToLastElement], null, extraRedundantCheck)),1713null,1714isFinished1715);17161717if (resultElement) {1718expr.push(resultElement);1719expr.sort(cmp);1720}1721}17221723if (expr.length === 1) {1724return expr[0];1725}17261727// resolve false AND expressions1728if (extraRedundantCheck) {1729for (let i = 0; i < expr.length; i++) {1730for (let j = i + 1; j < expr.length; j++) {1731if (expr[i].negate().equals(expr[j])) {1732// A && !A case1733return ContextKeyFalseExpr.INSTANCE;1734}1735}1736}17371738if (expr.length === 1) {1739return expr[0];1740}1741}17421743return new ContextKeyAndExpr(expr, negated);1744}17451746public serialize(): string {1747return this.expr.map(e => e.serialize()).join(' && ');1748}17491750public keys(): string[] {1751const result: string[] = [];1752for (const expr of this.expr) {1753result.push(...expr.keys());1754}1755return result;1756}17571758public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1759return new ContextKeyAndExpr(this.expr.map(expr => expr.map(mapFnc)), null);1760}17611762public negate(): ContextKeyExpression {1763if (!this.negated) {1764const result: ContextKeyExpression[] = [];1765for (const expr of this.expr) {1766result.push(expr.negate());1767}1768this.negated = ContextKeyOrExpr.create(result, this, true)!;1769}1770return this.negated;1771}1772}17731774export class ContextKeyOrExpr implements IContextKeyExpression {17751776public static create(_expr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {1777return ContextKeyOrExpr._normalizeArr(_expr, negated, extraRedundantCheck);1778}17791780public readonly type = ContextKeyExprType.Or;17811782private constructor(1783public readonly expr: ContextKeyExpression[],1784private negated: ContextKeyExpression | null1785) {1786}17871788public cmp(other: ContextKeyExpression): number {1789if (other.type !== this.type) {1790return this.type - other.type;1791}1792if (this.expr.length < other.expr.length) {1793return -1;1794}1795if (this.expr.length > other.expr.length) {1796return 1;1797}1798for (let i = 0, len = this.expr.length; i < len; i++) {1799const r = cmp(this.expr[i], other.expr[i]);1800if (r !== 0) {1801return r;1802}1803}1804return 0;1805}18061807public equals(other: ContextKeyExpression): boolean {1808if (other.type === this.type) {1809if (this.expr.length !== other.expr.length) {1810return false;1811}1812for (let i = 0, len = this.expr.length; i < len; i++) {1813if (!this.expr[i].equals(other.expr[i])) {1814return false;1815}1816}1817return true;1818}1819return false;1820}18211822public substituteConstants(): ContextKeyExpression | undefined {1823const exprArr = eliminateConstantsInArray(this.expr);1824if (exprArr === this.expr) {1825// no change1826return this;1827}1828return ContextKeyOrExpr.create(exprArr, this.negated, false);1829}18301831public evaluate(context: IContext): boolean {1832for (let i = 0, len = this.expr.length; i < len; i++) {1833if (this.expr[i].evaluate(context)) {1834return true;1835}1836}1837return false;1838}18391840private static _normalizeArr(arr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {1841let expr: ContextKeyExpression[] = [];1842let hasFalse = false;18431844if (arr) {1845for (let i = 0, len = arr.length; i < len; i++) {1846const e = arr[i];1847if (!e) {1848continue;1849}18501851if (e.type === ContextKeyExprType.False) {1852// anything || false ==> anything1853hasFalse = true;1854continue;1855}18561857if (e.type === ContextKeyExprType.True) {1858// anything || true ==> true1859return ContextKeyTrueExpr.INSTANCE;1860}18611862if (e.type === ContextKeyExprType.Or) {1863expr = expr.concat(e.expr);1864continue;1865}18661867expr.push(e);1868}18691870if (expr.length === 0 && hasFalse) {1871return ContextKeyFalseExpr.INSTANCE;1872}18731874expr.sort(cmp);1875}18761877if (expr.length === 0) {1878return undefined;1879}18801881if (expr.length === 1) {1882return expr[0];1883}18841885// eliminate duplicate terms1886for (let i = 1; i < expr.length; i++) {1887if (expr[i - 1].equals(expr[i])) {1888expr.splice(i, 1);1889i--;1890}1891}18921893if (expr.length === 1) {1894return expr[0];1895}18961897// resolve true OR expressions1898if (extraRedundantCheck) {1899for (let i = 0; i < expr.length; i++) {1900for (let j = i + 1; j < expr.length; j++) {1901if (expr[i].negate().equals(expr[j])) {1902// A || !A case1903return ContextKeyTrueExpr.INSTANCE;1904}1905}1906}19071908if (expr.length === 1) {1909return expr[0];1910}1911}19121913return new ContextKeyOrExpr(expr, negated);1914}19151916public serialize(): string {1917return this.expr.map(e => e.serialize()).join(' || ');1918}19191920public keys(): string[] {1921const result: string[] = [];1922for (const expr of this.expr) {1923result.push(...expr.keys());1924}1925return result;1926}19271928public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {1929return new ContextKeyOrExpr(this.expr.map(expr => expr.map(mapFnc)), null);1930}19311932public negate(): ContextKeyExpression {1933if (!this.negated) {1934const result: ContextKeyExpression[] = [];1935for (const expr of this.expr) {1936result.push(expr.negate());1937}19381939// We don't support parens, so here we distribute the AND over the OR terminals1940// We always take the first 2 AND pairs and distribute them1941while (result.length > 1) {1942const LEFT = result.shift()!;1943const RIGHT = result.shift()!;19441945const all: ContextKeyExpression[] = [];1946for (const left of getTerminals(LEFT)) {1947for (const right of getTerminals(RIGHT)) {1948all.push(ContextKeyAndExpr.create([left, right], null, false)!);1949}1950}19511952result.unshift(ContextKeyOrExpr.create(all, null, false)!);1953}19541955this.negated = ContextKeyOrExpr.create(result, this, true)!;1956}1957return this.negated;1958}1959}19601961export interface ContextKeyInfo {1962readonly key: string;1963readonly type?: string;1964readonly description?: string;1965}19661967export class RawContextKey<T extends ContextKeyValue> extends ContextKeyDefinedExpr {19681969private static _info: ContextKeyInfo[] = [];19701971static all(): IterableIterator<ContextKeyInfo> {1972return RawContextKey._info.values();1973}19741975private readonly _defaultValue: T | undefined;19761977constructor(key: string, defaultValue: T | undefined, metaOrHide?: string | true | { type: string; description: string }) {1978super(key, null);1979this._defaultValue = defaultValue;19801981// collect all context keys into a central place1982if (typeof metaOrHide === 'object') {1983RawContextKey._info.push({ ...metaOrHide, key });1984} else if (metaOrHide !== true) {1985RawContextKey._info.push({ key, description: metaOrHide, type: defaultValue !== null && defaultValue !== undefined ? typeof defaultValue : undefined });1986}1987}19881989public bindTo(target: IContextKeyService): IContextKey<T> {1990return target.createKey(this.key, this._defaultValue);1991}19921993public getValue(target: IContextKeyService): T | undefined {1994return target.getContextKeyValue<T>(this.key);1995}19961997public toNegated(): ContextKeyExpression {1998return this.negate();1999}20002001public isEqualTo(value: any): ContextKeyExpression {2002return ContextKeyEqualsExpr.create(this.key, value);2003}20042005public notEqualsTo(value: any): ContextKeyExpression {2006return ContextKeyNotEqualsExpr.create(this.key, value);2007}20082009public greater(value: any): ContextKeyExpression {2010return ContextKeyGreaterExpr.create(this.key, value);2011}2012}20132014export type ContextKeyValue = null | undefined | boolean | number | string2015| Array<null | undefined | boolean | number | string>2016| Record<string, null | undefined | boolean | number | string>;20172018export interface IContext {2019getValue<T extends ContextKeyValue = ContextKeyValue>(key: string): T | undefined;2020}20212022export interface IContextKey<T extends ContextKeyValue = ContextKeyValue> {2023set(value: T): void;2024reset(): void;2025get(): T | undefined;2026}20272028export interface IContextKeyServiceTarget {2029parentElement: IContextKeyServiceTarget | null;2030setAttribute(attr: string, value: string): void;2031removeAttribute(attr: string): void;2032hasAttribute(attr: string): boolean;2033getAttribute(attr: string): string | null;2034}20352036export const IContextKeyService = createDecorator<IContextKeyService>('contextKeyService');20372038export interface IReadableSet<T> {2039has(value: T): boolean;2040}20412042export interface IContextKeyChangeEvent {2043affectsSome(keys: IReadableSet<string>): boolean;2044allKeysContainedIn(keys: IReadableSet<string>): boolean;2045}20462047export type IScopedContextKeyService = IContextKeyService & IDisposable;20482049export interface IContextKeyService {2050readonly _serviceBrand: undefined;20512052readonly onDidChangeContext: Event<IContextKeyChangeEvent>;2053bufferChangeEvents(callback: Function): void;20542055createKey<T extends ContextKeyValue>(key: string, defaultValue: T | undefined): IContextKey<T>;2056contextMatchesRules(rules: ContextKeyExpression | undefined): boolean;2057getContextKeyValue<T>(key: string): T | undefined;20582059createScoped(target: IContextKeyServiceTarget): IScopedContextKeyService;2060createOverlay(overlay: Iterable<[string, any]>): IContextKeyService;2061getContext(target: IContextKeyServiceTarget | null): IContext;20622063updateParent(parentContextKeyService: IContextKeyService): void;2064}20652066function cmp1(key1: string, key2: string): number {2067if (key1 < key2) {2068return -1;2069}2070if (key1 > key2) {2071return 1;2072}2073return 0;2074}20752076function cmp2(key1: string, value1: any, key2: string, value2: any): number {2077if (key1 < key2) {2078return -1;2079}2080if (key1 > key2) {2081return 1;2082}2083if (value1 < value2) {2084return -1;2085}2086if (value1 > value2) {2087return 1;2088}2089return 0;2090}20912092/**2093* Returns true if it is provable `p` implies `q`.2094*/2095export function implies(p: ContextKeyExpression, q: ContextKeyExpression): boolean {20962097if (p.type === ContextKeyExprType.False || q.type === ContextKeyExprType.True) {2098// false implies anything2099// anything implies true2100return true;2101}21022103if (p.type === ContextKeyExprType.Or) {2104if (q.type === ContextKeyExprType.Or) {2105// `a || b || c` can only imply something like `a || b || c || d`2106return allElementsIncluded(p.expr, q.expr);2107}2108return false;2109}21102111if (q.type === ContextKeyExprType.Or) {2112for (const element of q.expr) {2113if (implies(p, element)) {2114return true;2115}2116}2117return false;2118}21192120if (p.type === ContextKeyExprType.And) {2121if (q.type === ContextKeyExprType.And) {2122// `a && b && c` implies `a && c`2123return allElementsIncluded(q.expr, p.expr);2124}2125for (const element of p.expr) {2126if (implies(element, q)) {2127return true;2128}2129}2130return false;2131}21322133return p.equals(q);2134}21352136/**2137* Returns true if all elements in `p` are also present in `q`.2138* The two arrays are assumed to be sorted2139*/2140function allElementsIncluded(p: ContextKeyExpression[], q: ContextKeyExpression[]): boolean {2141let pIndex = 0;2142let qIndex = 0;2143while (pIndex < p.length && qIndex < q.length) {2144const cmp = p[pIndex].cmp(q[qIndex]);21452146if (cmp < 0) {2147// an element from `p` is missing from `q`2148return false;2149} else if (cmp === 0) {2150pIndex++;2151qIndex++;2152} else {2153qIndex++;2154}2155}2156return (pIndex === p.length);2157}21582159function getTerminals(node: ContextKeyExpression) {2160if (node.type === ContextKeyExprType.Or) {2161return node.expr;2162}2163return [node];2164}216521662167