feat(completion): complete mongo arrayFilters and pull

This commit is contained in:
Loc Nguyen authored and GitHub committed 2026-10-02 09:11:03 +08:00
1 parent 472f70b218
commit e0f2072bef
2 files changed
+33 -1

No files matched your search

+17 -1
View File
@@ -43,6 +43,7 @@ export type MongoCompletionMode =
| "cursorMethod"
| "field"
| "filterField"
| "pullCondition"
| "fieldPath"
| "fieldRef"
| "value"
@@ -476,6 +477,10 @@ export function buildMongoCompletionItemsFromContext(context: MongoCompletionCon
// Fields lead; `$and` / `$or` and the other whole-filter operators follow once `$` is typed.
items = [...fieldItems(prefix, fields), ...specItems(TOP_LEVEL_QUERY_OPERATORS, prefix, "query operator", 80)];
break;
case "pullCondition":
// Fields lead; query operators follow once `$` is typed.
items = [...fieldItems(prefix, fields), ...specItems(FIELD_QUERY_OPERATORS, prefix, "query operator", 80)];
break;
case "fieldPath":
items = fieldPathItems(prefix, fields);
break;
@@ -543,7 +548,7 @@ export function buildMongoCompletionItemsFromContext(context: MongoCompletionCon
/** Modes whose items are built from the target collection's sampled fields. */
export function mongoCompletionNeedsFields(mode: MongoCompletionMode): boolean {
return mode === "field" || mode === "filterField" || mode === "fieldPath" || mode === "fieldRef" || mode === "expression";
return mode === "field" || mode === "filterField" || mode === "pullCondition" || mode === "fieldPath" || mode === "fieldRef" || mode === "expression";
}
/** Modes whose items are built from the database's collection names. */
@@ -990,6 +995,16 @@ function classifyUpdateArgument(scan: MongoCallScan, rootIndex: number): MongoCu
function classifyUpdate(scan: MongoCallScan, rootIndex: number): MongoCursorClass {
const inner = innermost(scan);
if (!inner || innerDepth(scan, rootIndex) < 0) return { mode: "none" };
const pullIndex = findContainerIndex(scan, rootIndex, "$pull");
if (pullIndex >= 0 && scan.stack.length > pullIndex + 1) {
const classified = classifyFilter(scan, pullIndex + 1);
if (innerDepth(scan, pullIndex + 1) === 0 && classified.mode === "filterField") {
return { ...classified, mode: "pullCondition" };
}
return classified;
}
const parent = scan.stack[scan.stack.length - 2];
if (scan.inValue) {
// `$currentDate: { at: { $type: "timestamp" } }`.
@@ -1067,6 +1082,7 @@ function classifyMethodOptions(method: string, scan: MongoCallScan): MongoCursor
return { mode: scan.inString ? "none" : "keyMapValue", method, keyMap: option };
}
if (FILTER_OPTION_KEYS.has(option)) return { ...classifyFilter(scan, 1), method };
if (option === "arrayFilters") return { ...classifyFilter(scan, 2), method };
if (option === "pipeline" && scan.stack[1]?.kind === "array") {
return { ...classifyPipeline(scan, findPipelineArrayIndex(scan.stack), method === "createCollection" ? "view" : "aggregate"), method };
}
+16
View File
@@ -457,6 +457,16 @@ test("suggests fields under an update operator and array modifiers under $push",
const modifiers = labels("db.users.updateOne({}, { $push: { tags: { ", { fields });
assert.ok(modifiers.includes("$each"));
assert.equal(modifiers.includes("$set"), false);
const pullOperators = labels("db.users.updateOne({}, { $pull: { tags: { $", { fields });
assert.ok(pullOperators.includes("$gte"));
assert.ok(pullOperators.includes("$in"));
assert.equal(pullOperators.includes("$set"), false);
assert.equal(pullOperators.includes("name"), false);
assert.deepEqual(labels("db.users.updateOne({}, { $pull: { tags: { na", { fields }), ["name"]);
assert.ok(labels("db.users.updateOne({}, { $pull: { tags: { $in: [").includes("ObjectId"));
assert.equal(labels("db.users.updateOne({}, { $pullAll: { tags: [").length, 0);
});
test("suggests fields inside a $match stage rather than operators", () => {
@@ -690,6 +700,12 @@ test("suggests the option keys of methods that take an options argument", () =>
// A `sort` or `projection` option holds field names.
assert.deepEqual(labels("db.users.findOneAndUpdate({}, {$set:{a:1}}, { sort: { na", { fields }), ["name"]);
assert.deepEqual(labels("db.users.findOneAndDelete({}, { projection: { na", { fields }), ["name"]);
// An `arrayFilters` option holds filter documents.
assert.deepEqual(labels('db.users.updateOne({}, { $set: { "tags.$[e].x": 1 } }, { arrayFilters: [{ na', { fields }), ["name"]);
assert.ok(labels('db.users.updateOne({}, { $set: { "tags.$[e].x": 1 } }, { arrayFilters: [{ "tags.x": { $', { fields }).includes("$gte"));
assert.deepEqual(labels('db.users.updateMany({}, { $set: { "tags.$[e].x": 1 } }, { arrayFilters: [{ na', { fields }), ["name"]);
assert.deepEqual(labels('db.users.findOneAndUpdate({}, { $set: { "tags.$[e].x": 1 } }, { arrayFilters: [{ na', { fields }), ["name"]);
});
test("stays quiet in the trailing argument of methods that take no options", () => {