Skip to content

Commit 50a07fe

Browse files
committed
adjust for #90 (used variables - scopes)
note: internal usedby gets merged, thats why i added both tasks
1 parent 8d34c58 commit 50a07fe

File tree

3 files changed

+151
-31
lines changed

3 files changed

+151
-31
lines changed

lib/base/VariableResolver.js

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,26 @@ export class BaseVariableResolver {
287287
});
288288
});
289289

290-
const reversedVariables = [ ...scopeVariables, ...parentsScopeVariables ].reverse();
290+
// (3) include descendant-scoped variables that are used outside their
291+
// own scope but still within the current scope (cross-scope leak)
292+
const leakedVariables = allVariables.filter(variable => {
293+
return variable.scope
294+
&& variable.scope.id !== bo.id
295+
&& isElementInScope(variable.scope, bo)
296+
&& isUsedInScope(variable, bo)
297+
&& isUsedOutsideOwnScope(variable);
298+
});
299+
300+
const reversedVariables = [ ...leakedVariables, ...scopeVariables, ...parentsScopeVariables ].reverse();
291301

292302
const seenNames = new Set();
293303

294304
return reversedVariables.filter(variable => {
295305

306+
const provider = variable.provider || [];
307+
296308
// if external variable, keep
297-
if (variable.provider.find(extractor => extractor !== this._baseExtractor)) {
309+
if (provider.find(extractor => extractor !== this._baseExtractor)) {
298310
return true;
299311
}
300312

@@ -329,9 +341,8 @@ export class BaseVariableResolver {
329341
const allVariables = Object.values(allVariablesByRoot).flat();
330342

331343
return allVariables.filter(v =>
332-
v.usedBy && v.usedBy.length > 0
333-
&& !v.scope
334-
&& v.origin.length === 1 && v.origin[0].id === element.id
344+
!v.scope
345+
&& v.usedBy && v.usedBy.some((a) => a.id === element.id)
335346
);
336347
}
337348
}
@@ -431,4 +442,34 @@ function cloneVariable(variable) {
431442
}
432443

433444
return newVariable;
445+
}
446+
447+
function isUsedInScope(variable, scopeElement) {
448+
if (!variable.usedBy || !Array.isArray(variable.usedBy)) {
449+
return false;
450+
}
451+
452+
return variable.usedBy.some(usedBy => isElementInScope(usedBy, scopeElement));
453+
}
454+
455+
function isElementInScope(element, scopeElement) {
456+
if (!element || !element.id || !scopeElement || !scopeElement.id) {
457+
return false;
458+
}
459+
460+
if (element.id === scopeElement.id) {
461+
return true;
462+
}
463+
464+
return getParents(element).some(parent => parent.id === scopeElement.id);
465+
}
466+
467+
function isUsedOutsideOwnScope(variable) {
468+
if (!variable.scope || !Array.isArray(variable.usedBy)) {
469+
return false;
470+
}
471+
472+
return variable.usedBy.some(usedBy => {
473+
return usedBy && usedBy.id && !isElementInScope(usedBy, variable.scope);
474+
});
434475
}

lib/zeebe/util/feelUtility.js

Lines changed: 103 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,83 @@ export function parseVariables(variables) {
8282

8383
// Step 4 - Annotate locally-provided variables with usedBy information
8484
for (const { variableName, targetName, origin } of localUsages) {
85-
const variable = variables.find(v =>
86-
v.name === variableName && v.scope === origin
87-
);
85+
const variable = findNearestScopedVariable(variables, variableName, origin);
8886

89-
if (variable) {
90-
if (!variable.usedBy) {
91-
variable.usedBy = [];
87+
if (!variable) {
88+
continue;
89+
}
90+
91+
// Keep existing behavior for same-origin mappings (`usedBy: [ 'targetVar' ]`)
92+
// and use the consuming element for ancestor-scoped variables.
93+
const usage = variable.scope === origin ? targetName : origin;
94+
95+
if (!variable.usedBy) {
96+
variable.usedBy = [];
97+
}
98+
99+
if (!hasUsage(variable.usedBy, usage)) {
100+
variable.usedBy.push(usage);
101+
}
102+
}
103+
104+
// Step 5 - Bridge consumed usages back to uniquely scoped declarations
105+
annotateConsumedUsagesToScopedVariables(variables, consumedVariables);
106+
107+
return { resolvedVariables, consumedVariables };
108+
}
109+
110+
function annotateConsumedUsagesToScopedVariables(variables, consumedVariables) {
111+
for (const consumedVariable of consumedVariables) {
112+
const scopedCandidates = variables.filter(v => v.name === consumedVariable.name && v.scope);
113+
114+
if (scopedCandidates.length !== 1) {
115+
continue;
116+
}
117+
118+
const scopedVariable = scopedCandidates[0];
119+
120+
if (!scopedVariable.usedBy) {
121+
scopedVariable.usedBy = [];
122+
}
123+
124+
for (const usage of consumedVariable.usedBy || []) {
125+
if (!usage || !usage.id) {
126+
continue;
92127
}
93128

94-
if (!variable.usedBy.includes(targetName)) {
95-
variable.usedBy.push(targetName);
129+
if (!hasUsage(scopedVariable.usedBy, usage)) {
130+
scopedVariable.usedBy.push(usage);
96131
}
97132
}
98133
}
134+
}
99135

100-
return { resolvedVariables, consumedVariables };
136+
function findNearestScopedVariable(variables, variableName, origin) {
137+
const scopes = [ origin, ...getParents(origin) ];
138+
139+
for (const scope of scopes) {
140+
const variable = variables.find(v => v.name === variableName && v.scope === scope);
141+
142+
if (variable) {
143+
return variable;
144+
}
145+
}
146+
147+
return null;
148+
}
149+
150+
function hasUsage(usages, usage) {
151+
return usages.some(existing => {
152+
if (existing === usage) {
153+
return true;
154+
}
155+
156+
if (typeof existing === 'string' && typeof usage === 'string') {
157+
return existing === usage;
158+
}
159+
160+
return existing && usage && existing.id && usage.id && existing.id === usage.id;
161+
});
101162
}
102163

103164
/**
@@ -669,21 +730,12 @@ function buildConsumedVariables(analysisResults) {
669730
const inputMappingTargetsCache = {};
670731

671732
for (const { origin, targetName, inputs, expressionType } of analysisResults) {
672-
673-
if (!inputMappingTargetsCache[origin.id]) {
674-
inputMappingTargetsCache[origin.id] = getInputMappingTargetNames(origin);
675-
}
676-
const orderedTargets = inputMappingTargetsCache[origin.id];
677-
678-
// Input mappings are order-sensitive: only earlier targets are available.
679-
// Scripts can reference all input mapping targets.
680-
let availableLocalTargets;
681-
if (expressionType === 'input-mapping') {
682-
const targetIndex = orderedTargets.indexOf(targetName);
683-
availableLocalTargets = new Set(orderedTargets.slice(0, targetIndex));
684-
} else {
685-
availableLocalTargets = new Set(orderedTargets);
686-
}
733+
const availableLocalTargets = getAvailableLocalTargets(
734+
origin,
735+
expressionType,
736+
targetName,
737+
inputMappingTargetsCache
738+
);
687739

688740
for (const inputVar of inputs) {
689741

@@ -735,4 +787,31 @@ function getInputMappingTargetNames(origin) {
735787
return ioMapping.inputParameters.map(p => p.target);
736788
}
737789

790+
function getAvailableLocalTargets(origin, expressionType, targetName, inputMappingTargetsCache) {
791+
const availableTargets = new Set();
792+
const scopes = [ origin, ...getParents(origin) ];
793+
794+
for (const scope of scopes) {
795+
if (!inputMappingTargetsCache[scope.id]) {
796+
inputMappingTargetsCache[scope.id] = getInputMappingTargetNames(scope);
797+
}
798+
799+
const orderedTargets = inputMappingTargetsCache[scope.id];
800+
801+
// Input mappings on the current element are order-sensitive; ancestor
802+
// mappings are already established and fully available.
803+
if (scope === origin && expressionType === 'input-mapping') {
804+
const targetIndex = orderedTargets.indexOf(targetName);
805+
const availableOwnTargets = targetIndex === -1 ? orderedTargets : orderedTargets.slice(0, targetIndex);
806+
807+
availableOwnTargets.forEach(target => availableTargets.add(target));
808+
continue;
809+
}
810+
811+
orderedTargets.forEach(target => availableTargets.add(target));
812+
}
813+
814+
return availableTargets;
815+
}
816+
738817

test/spec/zeebe/ZeebeVariableResolver.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2631,7 +2631,7 @@ describe('ZeebeVariableResolver', function() {
26312631
// then
26322632
expect(variables).to.variableEqual([
26332633
{ name: 'taskResult' },
2634-
{ name: 'approved', usedBy: [ 'Task_2' ] }
2634+
{ name: 'approved', usedBy: [ 'Task_1', 'Task_2' ] }
26352635
]);
26362636
}));
26372637

@@ -2647,7 +2647,7 @@ describe('ZeebeVariableResolver', function() {
26472647
// then
26482648
expect(variables).to.variableEqual([
26492649
{ name: 'taskResult' },
2650-
{ name: 'approved', usedBy: [ 'Task_1' ] }
2650+
{ name: 'approved', usedBy: [ 'Task_1', 'Task_2' ] }
26512651
]);
26522652
}));
26532653

0 commit comments

Comments
 (0)