fix(editor): Amend missing nodeName passthroughs for codemirror completions (no-changelog) (#17292)

This commit is contained in:
Charlie Kolb
2025-07-14 16:33:19 +02:00
committed by GitHub
parent f1e03ab6b1
commit 987e0ecf09
3 changed files with 26 additions and 6 deletions

View File

@@ -40,6 +40,7 @@ import { useI18n } from '@n8n/i18n';
import { useWorkflowsStore } from '../stores/workflows.store'; import { useWorkflowsStore } from '../stores/workflows.store';
import { useAutocompleteTelemetry } from './useAutocompleteTelemetry'; import { useAutocompleteTelemetry } from './useAutocompleteTelemetry';
import { ignoreUpdateAnnotation } from '../utils/forceParse'; import { ignoreUpdateAnnotation } from '../utils/forceParse';
import { TARGET_NODE_PARAMETER_FACET } from '@/plugins/codemirror/completions/constants';
export const useExpressionEditor = ({ export const useExpressionEditor = ({
editorRef, editorRef,
@@ -205,6 +206,7 @@ export const useExpressionEditor = ({
const state = EditorState.create({ const state = EditorState.create({
doc: toValue(editorValue), doc: toValue(editorValue),
extensions: [ extensions: [
TARGET_NODE_PARAMETER_FACET.of(toValue(targetNodeParameterContext)),
customExtensions.value.of(toValue(extensions)), customExtensions.value.of(toValue(extensions)),
readOnlyExtensions.value.of([EditorState.readOnly.of(toValue(isReadOnly))]), readOnlyExtensions.value.of([EditorState.readOnly.of(toValue(isReadOnly))]),
telemetryExtensions.value.of([]), telemetryExtensions.value.of([]),

View File

@@ -2,6 +2,7 @@ import { prefixMatch, longestCommonPrefix, resolveAutocompleteExpression } from
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { Resolved } from './types'; import type { Resolved } from './types';
import { escapeMappingString } from '@/utils/mappingUtils'; import { escapeMappingString } from '@/utils/mappingUtils';
import { TARGET_NODE_PARAMETER_FACET } from './constants';
/** /**
* Resolution-based completions offered at the start of bracket access notation. * Resolution-based completions offered at the start of bracket access notation.
@@ -14,6 +15,7 @@ import { escapeMappingString } from '@/utils/mappingUtils';
* - `$input.first().json.myStr[|` * - `$input.first().json.myStr[|`
*/ */
export function bracketAccessCompletions(context: CompletionContext): CompletionResult | null { export function bracketAccessCompletions(context: CompletionContext): CompletionResult | null {
const targetNodeParameterContext = context.state.facet(TARGET_NODE_PARAMETER_FACET);
const word = context.matchBefore(/\$[\S\s]*\[.*/); const word = context.matchBefore(/\$[\S\s]*\[.*/);
if (!word) return null; if (!word) return null;
@@ -30,7 +32,10 @@ export function bracketAccessCompletions(context: CompletionContext): Completion
let resolved: Resolved; let resolved: Resolved;
try { try {
resolved = resolveAutocompleteExpression(`={{ ${base} }}`); resolved = resolveAutocompleteExpression(
`={{ ${base} }}`,
targetNodeParameterContext?.nodeName,
);
} catch { } catch {
return null; return null;
} }

View File

@@ -28,6 +28,7 @@ import {
RECOMMENDED_SECTION, RECOMMENDED_SECTION,
STRING_RECOMMENDED_OPTIONS, STRING_RECOMMENDED_OPTIONS,
STRING_SECTIONS, STRING_SECTIONS,
TARGET_NODE_PARAMETER_FACET,
} from './constants'; } from './constants';
import { createInfoBoxRenderer } from './infoBoxRenderer'; import { createInfoBoxRenderer } from './infoBoxRenderer';
import { luxonInstanceDocs } from './nativesAutocompleteDocs/luxon.instance.docs'; import { luxonInstanceDocs } from './nativesAutocompleteDocs/luxon.instance.docs';
@@ -56,11 +57,13 @@ import {
} from './utils'; } from './utils';
import { javascriptLanguage } from '@codemirror/lang-javascript'; import { javascriptLanguage } from '@codemirror/lang-javascript';
import { isPairedItemIntermediateNodesError } from '@/utils/expressions'; import { isPairedItemIntermediateNodesError } from '@/utils/expressions';
import type { TargetNodeParameterContext } from '@/Interface';
/** /**
* Resolution-based completions offered according to datatype. * Resolution-based completions offered according to datatype.
*/ */
export function datatypeCompletions(context: CompletionContext): CompletionResult | null { export function datatypeCompletions(context: CompletionContext): CompletionResult | null {
const targetNodeParameterContext = context.state.facet(TARGET_NODE_PARAMETER_FACET);
const word = context.matchBefore(DATATYPE_REGEX); const word = context.matchBefore(DATATYPE_REGEX);
if (!word) return null; if (!word) return null;
@@ -86,7 +89,8 @@ export function datatypeCompletions(context: CompletionContext): CompletionResul
options = secretProvidersOptions(); options = secretProvidersOptions();
} else { } else {
const resolved = attempt( const resolved = attempt(
(): Resolved => resolveAutocompleteExpression(`={{ ${base} }}`), (): Resolved =>
resolveAutocompleteExpression(`={{ ${base} }}`, targetNodeParameterContext?.nodeName),
(error) => { (error) => {
if (!isPairedItemIntermediateNodesError(error)) { if (!isPairedItemIntermediateNodesError(error)) {
return null; 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 // Fallback on first item to provide autocomplete when intermediate nodes have not run
return attempt(() => 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 // 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) // example: {{ $json.str| }} -> ['length', 'includes()'...] (would usually need a '.' suffix)
if (context.explicit && !word.text.endsWith('.') && options.length === 0) { if (context.explicit && !word.text.endsWith('.') && options.length === 0) {
options = explicitDataTypeOptions(word.text); options = explicitDataTypeOptions(word.text, targetNodeParameterContext);
from = word.to; 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( return attempt(
() => { () => {
const resolved = resolveAutocompleteExpression(`={{ ${expression} }}`); const resolved = resolveAutocompleteExpression(
`={{ ${expression} }}`,
targetNodeParameterContext?.nodeName,
);
return datatypeOptions({ return datatypeOptions({
resolved, resolved,
base: expression, base: expression,