Skip to content

Commit 01752ef

Browse files
committed
fix(variables): Fix resolution on double < (#2016)
* Fix variable <> * Ling * Clean
1 parent df4c6bd commit 01752ef

File tree

7 files changed

+90
-36
lines changed

7 files changed

+90
-36
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/code/code.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/comp
3535
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
3636
import { useWand } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-wand'
3737
import type { GenerationType } from '@/blocks/types'
38+
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
3839
import { useTagSelection } from '@/hooks/use-tag-selection'
3940
import { normalizeBlockName } from '@/stores/workflows/utils'
4041

@@ -99,14 +100,15 @@ const createHighlightFunction = (
99100
let processedCode = codeToHighlight
100101

101102
// Replace environment variables with placeholders
102-
processedCode = processedCode.replace(/\{\{([^}]+)\}\}/g, (match) => {
103+
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
103104
const placeholder = `__ENV_VAR_${placeholders.length}__`
104105
placeholders.push({ placeholder, original: match, type: 'env' })
105106
return placeholder
106107
})
107108

108109
// Replace variable references with placeholders
109-
processedCode = processedCode.replace(/<([^>]+)>/g, (match) => {
110+
// Use [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" should match separately)
111+
processedCode = processedCode.replace(createReferencePattern(), (match) => {
110112
if (shouldHighlightReference(match)) {
111113
const placeholder = `__VAR_REF_${placeholders.length}__`
112114
placeholders.push({ placeholder, original: match, type: 'var' })

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/condition-input/condition-input.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
3232
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
3333
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
34+
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
3435
import { useTagSelection } from '@/hooks/use-tag-selection'
3536
import { normalizeBlockName } from '@/stores/workflows/utils'
3637
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -869,7 +870,7 @@ export function ConditionInput({
869870
let processedCode = codeToHighlight
870871

871872
// Replace environment variables with placeholders
872-
processedCode = processedCode.replace(/\{\{([^}]+)\}\}/g, (match) => {
873+
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
873874
const placeholder = `__ENV_VAR_${placeholders.length}__`
874875
placeholders.push({
875876
placeholder,
@@ -881,20 +882,24 @@ export function ConditionInput({
881882
})
882883

883884
// Replace variable references with placeholders
884-
processedCode = processedCode.replace(/<([^>]+)>/g, (match) => {
885-
const shouldHighlight = shouldHighlightReference(match)
886-
if (shouldHighlight) {
887-
const placeholder = `__VAR_REF_${placeholders.length}__`
888-
placeholders.push({
889-
placeholder,
890-
original: match,
891-
type: 'var',
892-
shouldHighlight: true,
893-
})
894-
return placeholder
885+
// Use [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" should match separately)
886+
processedCode = processedCode.replace(
887+
createReferencePattern(),
888+
(match) => {
889+
const shouldHighlight = shouldHighlightReference(match)
890+
if (shouldHighlight) {
891+
const placeholder = `__VAR_REF_${placeholders.length}__`
892+
placeholders.push({
893+
placeholder,
894+
original: match,
895+
type: 'var',
896+
shouldHighlight: true,
897+
})
898+
return placeholder
899+
}
900+
return match
895901
}
896-
return match
897-
})
902+
)
898903

899904
// Apply Prism syntax highlighting
900905
let highlightedCode = highlight(

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/formatted-text.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import type { ReactNode } from 'react'
44
import { splitReferenceSegment } from '@/lib/workflows/references'
5+
import { REFERENCE } from '@/executor/consts'
6+
import { createCombinedPattern } from '@/executor/utils/reference-validation'
57
import { normalizeBlockName } from '@/stores/workflows/utils'
68

79
export interface HighlightContext {
@@ -43,7 +45,9 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
4345
}
4446

4547
const nodes: ReactNode[] = []
46-
const regex = /<[^>]+>|\{\{[^}]+\}\}/g
48+
// Match variable references without allowing nested brackets to prevent matching across references
49+
// e.g., "<3. text <real.ref>" should match "<3" and "<real.ref>", not the whole string
50+
const regex = createCombinedPattern()
4751
let lastIndex = 0
4852
let key = 0
4953

@@ -61,7 +65,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
6165
pushPlainText(text.slice(lastIndex, index))
6266
}
6367

64-
if (matchText.startsWith('{{')) {
68+
if (matchText.startsWith(REFERENCE.ENV_VAR_START)) {
6569
nodes.push(
6670
<span key={key++} className='text-[#34B5FF] dark:text-[#34B5FF]'>
6771
{matchText}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/hooks/use-subflow-editor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '@/lib/workflows/references'
88
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
99
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
10+
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
1011
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
1112
import { normalizeBlockName } from '@/stores/workflows/utils'
1213
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -133,13 +134,14 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
133134

134135
let processedCode = code
135136

136-
processedCode = processedCode.replace(/\{\{([^}]+)\}\}/g, (match) => {
137+
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
137138
const placeholder = `__ENV_VAR_${placeholders.length}__`
138139
placeholders.push({ placeholder, original: match, type: 'env' })
139140
return placeholder
140141
})
141142

142-
processedCode = processedCode.replace(/<[^>]+>/g, (match) => {
143+
// Use [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" should match separately)
144+
processedCode = processedCode.replace(createReferencePattern(), (match) => {
143145
if (shouldHighlightReference(match)) {
144146
const placeholder = `__VAR_REF_${placeholders.length}__`
145147
placeholders.push({ placeholder, original: match, type: 'var' })

apps/sim/executor/orchestrators/loop.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { LoopScope } from '@/executor/execution/state'
55
import type { BlockStateController } from '@/executor/execution/types'
66
import type { ExecutionContext, NormalizedBlockOutput } from '@/executor/types'
77
import type { LoopConfigWithNodes } from '@/executor/types/loop'
8+
import { replaceValidReferences } from '@/executor/utils/reference-validation'
89
import {
910
buildSentinelEndId,
1011
buildSentinelStartId,
@@ -271,16 +272,14 @@ export class LoopOrchestrator {
271272
}
272273

273274
try {
274-
const referencePattern = /<([^>]+)>/g
275-
let evaluatedCondition = condition
276-
277275
logger.info('Evaluating loop condition', {
278276
originalCondition: condition,
279277
iteration: scope.iteration,
280278
workflowVariables: ctx.workflowVariables,
281279
})
282280

283-
evaluatedCondition = evaluatedCondition.replace(referencePattern, (match) => {
281+
// Use generic utility for smart variable reference replacement
282+
const evaluatedCondition = replaceValidReferences(condition, (match) => {
284283
const resolved = this.resolver.resolveSingleReference(ctx, '', match, scope)
285284
logger.info('Resolved variable reference in loop condition', {
286285
reference: match,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { isLikelyReferenceSegment } from '@/lib/workflows/references'
2+
import { REFERENCE } from '@/executor/consts'
3+
4+
/**
5+
* Creates a regex pattern for matching variable references.
6+
* Uses [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" matches separately).
7+
*/
8+
export function createReferencePattern(): RegExp {
9+
return new RegExp(
10+
`${REFERENCE.START}([^${REFERENCE.START}${REFERENCE.END}]+)${REFERENCE.END}`,
11+
'g'
12+
)
13+
}
14+
15+
/**
16+
* Creates a regex pattern for matching environment variables {{variable}}
17+
*/
18+
export function createEnvVarPattern(): RegExp {
19+
return new RegExp(`\\${REFERENCE.ENV_VAR_START}([^}]+)\\${REFERENCE.ENV_VAR_END}`, 'g')
20+
}
21+
22+
/**
23+
* Combined pattern matching both <reference> and {{env_var}}
24+
*/
25+
export function createCombinedPattern(): RegExp {
26+
return new RegExp(
27+
`${REFERENCE.START}[^${REFERENCE.START}${REFERENCE.END}]+${REFERENCE.END}|` +
28+
`\\${REFERENCE.ENV_VAR_START}[^}]+\\${REFERENCE.ENV_VAR_END}`,
29+
'g'
30+
)
31+
}
32+
33+
/**
34+
* Replaces variable references with smart validation.
35+
* Distinguishes < operator from < bracket using isLikelyReferenceSegment.
36+
*/
37+
export function replaceValidReferences(
38+
template: string,
39+
replacer: (match: string) => string
40+
): string {
41+
const pattern = createReferencePattern()
42+
43+
return template.replace(pattern, (match) => {
44+
if (!isLikelyReferenceSegment(match)) {
45+
return match
46+
}
47+
return replacer(match)
48+
})
49+
}

apps/sim/executor/variables/resolver.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createLogger } from '@/lib/logs/console/logger'
22
import { BlockType, REFERENCE } from '@/executor/consts'
33
import type { ExecutionState, LoopScope } from '@/executor/execution/state'
44
import type { ExecutionContext } from '@/executor/types'
5+
import { replaceValidReferences } from '@/executor/utils/reference-validation'
56
import { BlockResolver } from '@/executor/variables/resolvers/block'
67
import { EnvResolver } from '@/executor/variables/resolvers/env'
78
import { LoopResolver } from '@/executor/variables/resolvers/loop'
@@ -147,21 +148,17 @@ export class VariableResolver {
147148
loopScope?: LoopScope,
148149
block?: SerializedBlock
149150
): string {
150-
let result = template
151151
const resolutionContext: ResolutionContext = {
152152
executionContext: ctx,
153153
executionState: this.state,
154154
currentNodeId,
155155
loopScope,
156156
}
157-
const referenceRegex = new RegExp(
158-
`${REFERENCE.START}([^${REFERENCE.END}]+)${REFERENCE.END}`,
159-
'g'
160-
)
161157

162158
let replacementError: Error | null = null
163159

164-
result = result.replace(referenceRegex, (match) => {
160+
// Use generic utility for smart variable reference replacement
161+
let result = replaceValidReferences(template, (match) => {
165162
if (replacementError) return match
166163

167164
try {
@@ -202,21 +199,17 @@ export class VariableResolver {
202199
template: string,
203200
loopScope?: LoopScope
204201
): string {
205-
let result = template
206202
const resolutionContext: ResolutionContext = {
207203
executionContext: ctx,
208204
executionState: this.state,
209205
currentNodeId,
210206
loopScope,
211207
}
212-
const referenceRegex = new RegExp(
213-
`${REFERENCE.START}([^${REFERENCE.END}]+)${REFERENCE.END}`,
214-
'g'
215-
)
216208

217209
let replacementError: Error | null = null
218210

219-
result = result.replace(referenceRegex, (match) => {
211+
// Use generic utility for smart variable reference replacement
212+
let result = replaceValidReferences(template, (match) => {
220213
if (replacementError) return match
221214

222215
try {

0 commit comments

Comments
 (0)