From 987e0ecf09cff7cb03358d67ae1d3c35016d610d Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Mon, 14 Jul 2025 16:33:19 +0200 Subject: [PATCH] fix(editor): Amend missing nodeName passthroughs for codemirror completions (no-changelog) (#17292) --- .../src/composables/useExpressionEditor.ts | 2 ++ .../completions/bracketAccess.completions.ts | 7 +++++- .../completions/datatype.completions.ts | 23 +++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/frontend/editor-ui/src/composables/useExpressionEditor.ts b/packages/frontend/editor-ui/src/composables/useExpressionEditor.ts index 2ffd126a111..63ba80d05c4 100644 --- a/packages/frontend/editor-ui/src/composables/useExpressionEditor.ts +++ b/packages/frontend/editor-ui/src/composables/useExpressionEditor.ts @@ -40,6 +40,7 @@ import { useI18n } from '@n8n/i18n'; import { useWorkflowsStore } from '../stores/workflows.store'; import { useAutocompleteTelemetry } from './useAutocompleteTelemetry'; import { ignoreUpdateAnnotation } from '../utils/forceParse'; +import { TARGET_NODE_PARAMETER_FACET } from '@/plugins/codemirror/completions/constants'; export const useExpressionEditor = ({ editorRef, @@ -205,6 +206,7 @@ export const useExpressionEditor = ({ const state = EditorState.create({ doc: toValue(editorValue), extensions: [ + TARGET_NODE_PARAMETER_FACET.of(toValue(targetNodeParameterContext)), customExtensions.value.of(toValue(extensions)), readOnlyExtensions.value.of([EditorState.readOnly.of(toValue(isReadOnly))]), telemetryExtensions.value.of([]), diff --git a/packages/frontend/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts b/packages/frontend/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts index 3f9e52b64a1..e3a4de3e1ed 100644 --- a/packages/frontend/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts +++ b/packages/frontend/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts @@ -2,6 +2,7 @@ import { prefixMatch, longestCommonPrefix, resolveAutocompleteExpression } from import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Resolved } from './types'; import { escapeMappingString } from '@/utils/mappingUtils'; +import { TARGET_NODE_PARAMETER_FACET } from './constants'; /** * Resolution-based completions offered at the start of bracket access notation. @@ -14,6 +15,7 @@ import { escapeMappingString } from '@/utils/mappingUtils'; * - `$input.first().json.myStr[|` */ export function bracketAccessCompletions(context: CompletionContext): CompletionResult | null { + const targetNodeParameterContext = context.state.facet(TARGET_NODE_PARAMETER_FACET); const word = context.matchBefore(/\$[\S\s]*\[.*/); if (!word) return null; @@ -30,7 +32,10 @@ export function bracketAccessCompletions(context: CompletionContext): Completion let resolved: Resolved; try { - resolved = resolveAutocompleteExpression(`={{ ${base} }}`); + resolved = resolveAutocompleteExpression( + `={{ ${base} }}`, + targetNodeParameterContext?.nodeName, + ); } catch { return null; } diff --git a/packages/frontend/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts b/packages/frontend/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts index 4816a9f02fc..d7d725e2b9f 100644 --- a/packages/frontend/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts +++ b/packages/frontend/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts @@ -28,6 +28,7 @@ import { RECOMMENDED_SECTION, STRING_RECOMMENDED_OPTIONS, STRING_SECTIONS, + TARGET_NODE_PARAMETER_FACET, } from './constants'; import { createInfoBoxRenderer } from './infoBoxRenderer'; import { luxonInstanceDocs } from './nativesAutocompleteDocs/luxon.instance.docs'; @@ -56,11 +57,13 @@ import { } from './utils'; import { javascriptLanguage } from '@codemirror/lang-javascript'; import { isPairedItemIntermediateNodesError } from '@/utils/expressions'; +import type { TargetNodeParameterContext } from '@/Interface'; /** * Resolution-based completions offered according to datatype. */ export function datatypeCompletions(context: CompletionContext): CompletionResult | null { + const targetNodeParameterContext = context.state.facet(TARGET_NODE_PARAMETER_FACET); const word = context.matchBefore(DATATYPE_REGEX); if (!word) return null; @@ -86,7 +89,8 @@ export function datatypeCompletions(context: CompletionContext): CompletionResul options = secretProvidersOptions(); } else { const resolved = attempt( - (): Resolved => resolveAutocompleteExpression(`={{ ${base} }}`), + (): Resolved => + resolveAutocompleteExpression(`={{ ${base} }}`, targetNodeParameterContext?.nodeName), (error) => { if (!isPairedItemIntermediateNodesError(error)) { return null; @@ -94,7 +98,10 @@ export function datatypeCompletions(context: CompletionContext): CompletionResul // Fallback on first item to provide autocomplete when intermediate nodes have not run return attempt(() => - resolveAutocompleteExpression(`={{ ${expressionWithFirstItem(syntaxTree, base)} }}`), + resolveAutocompleteExpression( + `={{ ${expressionWithFirstItem(syntaxTree, base)} }}`, + targetNodeParameterContext?.nodeName, + ), ); }, ); @@ -116,7 +123,7 @@ export function datatypeCompletions(context: CompletionContext): CompletionResul // When autocomplete is explicitely opened (by Ctrl+Space or programatically), add completions for the current word with '.' prefix // example: {{ $json.str| }} -> ['length', 'includes()'...] (would usually need a '.' suffix) if (context.explicit && !word.text.endsWith('.') && options.length === 0) { - options = explicitDataTypeOptions(word.text); + options = explicitDataTypeOptions(word.text, targetNodeParameterContext); from = word.to; } @@ -134,10 +141,16 @@ export function datatypeCompletions(context: CompletionContext): CompletionResul }; } -function explicitDataTypeOptions(expression: string): Completion[] { +function explicitDataTypeOptions( + expression: string, + targetNodeParameterContext?: TargetNodeParameterContext, +): Completion[] { return attempt( () => { - const resolved = resolveAutocompleteExpression(`={{ ${expression} }}`); + const resolved = resolveAutocompleteExpression( + `={{ ${expression} }}`, + targetNodeParameterContext?.nodeName, + ); return datatypeOptions({ resolved, base: expression,