1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 05:30:01 +00:00

Add type: filter operator

This commit is contained in:
Matt Gibson
2025-03-12 10:46:36 -07:00
parent 70de30cbf1
commit 0319320155
4 changed files with 50 additions and 2 deletions

View File

@@ -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";
}

View File

@@ -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 } } %}

View File

@@ -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"],

View File

@@ -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));
}