diff --git a/libs/common/src/vault/search/ast.ts b/libs/common/src/vault/search/ast.ts index e0d37afed3d..05889dfb4f3 100644 --- a/libs/common/src/vault/search/ast.ts +++ b/libs/common/src/vault/search/ast.ts @@ -15,6 +15,7 @@ export const AstNodeTypeNames = [ "inOrg", "inTrash", "isFavorite", + "type", ] as const; export type AstNodeType = (typeof AstNodeTypeNames)[number]; export type AstNode = @@ -33,7 +34,8 @@ export type AstNode = | InCollection | InOrg | InTrash - | IsFavorite; + | IsFavorite + | TypeFilter; type AstNodeBase = { d: object[]; @@ -182,3 +184,12 @@ export type IsFavorite = AstNodeBase & { export function isIsFavorite(x: AstNode): x is IsFavorite { return x.type === "isFavorite"; } + +export type TypeFilter = AstNodeBase & { + type: "type"; + cipherType: string; +}; + +export function isTypeFilter(x: AstNode): x is TypeFilter { + return x.type === "type"; +} diff --git a/libs/common/src/vault/search/bitwarden-query-grammar.ne b/libs/common/src/vault/search/bitwarden-query-grammar.ne index 2690cabb0e3..0b33d8ff631 100644 --- a/libs/common/src/vault/search/bitwarden-query-grammar.ne +++ b/libs/common/src/vault/search/bitwarden-query-grammar.ne @@ -15,6 +15,7 @@ let lexer = moo.compile({ func_has: 'has:', func_in: 'in:', func_is: 'is:', + func_type: 'type:', // function parameter separator access: ':', // string match, includes quoted strings with escaped quotes and backslashes @@ -59,6 +60,8 @@ TERM -> | %func_in "trash" {% function(d) { const start = d[0].offset; const length = 8; return { type: 'inTrash', d: d, start, end: start + length, length } } %} # only items marked as favorites | %func_is "favorite" {% function(d) { const start = d[0].offset; const length = 11; return { type: 'isFavorite', d: d, start, end: d[0].offset + length, length } } %} + # only items of given type type + | %func_type %string {% function(d) { const start = d[0].offset; const end = d[1].offset + d[1].value.length; return { type: 'type', d:d, cipherType: d[1].value, start, end, length: end - start + 1 } } %} # Boolean NOT operator | %NOT _ PARENTHESES {% function(d) { const start = d[0].offset; return { type: 'not', value: d[2], d: d, start, end: d[2].end, length: d[2].end - d[0].offset + 1 } } %} diff --git a/libs/common/src/vault/search/bitwarden-query-grammar.ts b/libs/common/src/vault/search/bitwarden-query-grammar.ts index 96eb107d2c4..85f21fda321 100644 --- a/libs/common/src/vault/search/bitwarden-query-grammar.ts +++ b/libs/common/src/vault/search/bitwarden-query-grammar.ts @@ -14,6 +14,7 @@ declare var access: any; declare var func_has: any; declare var func_in: any; declare var func_is: any; +declare var func_type: any; declare var NOT: any; declare var WS: any; @@ -32,6 +33,7 @@ let lexer = moo.compile({ func_has: "has:", func_in: "in:", func_is: "is:", + func_type: "type:", // function parameter separator access: ":", // string match, includes quoted strings with escaped quotes and backslashes @@ -278,6 +280,18 @@ const grammar: Grammar = { return { type: "isFavorite", d: d, start, end: d[0].offset + length, length }; }, }, + { + name: "TERM", + symbols: [ + lexer.has("func_type") ? { type: "func_type" } : func_type, + lexer.has("string") ? { type: "string" } : string, + ], + postprocess: function (d) { + const start = d[0].offset; + const end = d[1].offset + d[1].value.length; + return { type: "type", d: d, cipherType: d[1].value, start, end, length: end - start + 1 }; + }, + }, { name: "TERM", symbols: [lexer.has("NOT") ? { type: "NOT" } : NOT, "_", "PARENTHESES"], diff --git a/libs/common/src/vault/search/parse.ts b/libs/common/src/vault/search/parse.ts index 9ea2340e057..c2200c78bdd 100644 --- a/libs/common/src/vault/search/parse.ts +++ b/libs/common/src/vault/search/parse.ts @@ -1,7 +1,7 @@ import { Parser, Grammar } from "nearley"; import { Utils } from "../../platform/misc/utils"; -import { CardLinkedId, FieldType, LinkedIdType, LoginLinkedId } from "../enums"; +import { CardLinkedId, CipherType, FieldType, LinkedIdType, LoginLinkedId } from "../enums"; import { CipherView } from "../models/view/cipher.view"; import { @@ -21,6 +21,7 @@ import { isParentheses, isSearch, isTerm, + isTypeFilter, } from "./ast"; import grammar from "./bitwarden-query-grammar"; import { ProcessInstructions } from "./query.types"; @@ -294,6 +295,25 @@ function handleNode(node: AstNode): ProcessInstructions { }, ], }; + } else if (isTypeFilter(node)) { + const typeTest = fieldNameToRegexTest(node.cipherType); + return { + filter: (context) => ({ + ...context, + ciphers: context.ciphers.filter( + (cipher) => + typeTest.test(CipherType[cipher.type]) || + CipherType[node.cipherType as any] === CipherType[cipher.type], + ), + }), + sections: [ + { + start: node.start, + end: node.end, + type: node.type, + }, + ], + }; } else { throw new Error("Invalid node\n" + JSON.stringify(node, null, 2)); }