Skip to content

Commit 8b70dea

Browse files
authored
feat: expression string method autocomplete (#5421)
## Description Added autocomplete support for safe string and array methods in the expression editor, as suggested by @TrySound in PR #5403. ## Changes - Exported `allowedStringMethods` and `allowedArrayMethods` from `packages/sdk/src/expression.ts` - Enhanced the expression editor's autocomplete to suggest safe methods when typing after a variable (e.g., system) - Methods are displayed with parentheses for clarity (e.g., `toLowerCase()`, `replace()`, `split()`) - Autocomplete shows helpful labels: "string method" or "array method" ## How to Test 1. Open any project in the builder 2. Click the expression editor icon next to a text property 3. Type a variable name followed by a dot (e.g., `system.`) 4. You should see autocomplete suggestions for safe string methods like `toLowerCase()`, `replace()`, `split()`, etc. 5. Type a few letters to filter (e.g., `title.to` shows only methods starting with "to") 6. Select a method and it will be inserted with parentheses ## Related - Builds on PR #5403 which enabled these safe methods in the expression engine <img width="343" height="204" alt="Bildschirmfoto 2025-10-09 um 22 33 03" src="https://github.com/user-attachments/assets/206a36e8-1d68-4e2a-a045-1f9e95067a28" />
1 parent 631743b commit 8b70dea

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

apps/builder/app/builder/shared/expression-editor.tsx

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import {
2727
} from "@codemirror/autocomplete";
2828
import { javascript } from "@codemirror/lang-javascript";
2929
import { textVariants, css, rawTheme } from "@webstudio-is/design-system";
30-
import { decodeDataVariableId, lintExpression } from "@webstudio-is/sdk";
30+
import {
31+
decodeDataVariableId,
32+
lintExpression,
33+
allowedStringMethods,
34+
allowedArrayMethods,
35+
} from "@webstudio-is/sdk";
3136
import {
3237
EditorContent,
3338
EditorDialog,
@@ -244,6 +249,56 @@ const scopeCompletionSource: CompletionSource = (context) => {
244249
},
245250
});
246251
}
252+
253+
// Add safe string and array method completions
254+
if (path.path.length > 0) {
255+
const methodOptions: Completion[] = [];
256+
257+
// Determine if target is likely a string or array based on its value
258+
const isString = typeof target === "string";
259+
const isArray = Array.isArray(target);
260+
261+
// Add string methods only if target is a string
262+
if (isString) {
263+
for (const method of allowedStringMethods) {
264+
if (method.toLowerCase().includes(path.name.toLowerCase())) {
265+
methodOptions.push({
266+
label: `${method}()`,
267+
detail: "string method",
268+
type: "method",
269+
apply: (view, completion, from, to) => {
270+
view.dispatch({
271+
...insertCompletionText(view.state, `${method}()`, from, to),
272+
annotations: pickedCompletion.of(completion),
273+
});
274+
},
275+
});
276+
}
277+
}
278+
}
279+
280+
// Add array methods if target is an array
281+
if (isArray) {
282+
for (const method of allowedArrayMethods) {
283+
if (method.toLowerCase().includes(path.name.toLowerCase())) {
284+
methodOptions.push({
285+
label: `${method}()`,
286+
detail: "array method",
287+
type: "method",
288+
apply: (view, completion, from, to) => {
289+
view.dispatch({
290+
...insertCompletionText(view.state, `${method}()`, from, to),
291+
annotations: pickedCompletion.of(completion),
292+
});
293+
},
294+
});
295+
}
296+
}
297+
}
298+
299+
options = [...options, ...methodOptions];
300+
}
301+
247302
return {
248303
from: context.pos - path.name.length,
249304
filter: false,

packages/sdk/src/expression.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type ExpressionVisitor = {
2929
[K in Expression["type"]]: (node: Extract<Expression, { type: K }>) => void;
3030
};
3131

32-
const allowedStringMethods = new Set([
32+
export const allowedStringMethods = new Set([
3333
"toLowerCase",
3434
"replace",
3535
"split",
@@ -42,7 +42,7 @@ const allowedStringMethods = new Set([
4242
"toLocaleUpperCase",
4343
]);
4444

45-
const allowedArrayMethods = new Set(["at", "includes", "join", "slice"]);
45+
export const allowedArrayMethods = new Set(["at", "includes", "join", "slice"]);
4646

4747
export const lintExpression = ({
4848
expression,

0 commit comments

Comments
 (0)