Power Platform QA Solution
Power Platform QA
Home
Solutions
Reports
Settings
User Guide
Back to Analyzers
Configure Analyzer —
Non-Delegable Operations
CUSTOM
Metadata
ID
non-delegable-operations
Result Key
nonDelegableOps
File
non-delegable-operations.js
Built-in
No
Analyzer Classification
Category
— This determines the type of Analyzer you are building. Categories can be set up under settings.
Uncategorized
Performance
Data Operations
Variables & Scoping
Maintenance
Test sets
— Include this analyzer in predefined runs. Select one or more test sets this analyzer should belong to.
Dataverse test set
Some tests
Description
Detects non-delegable constructs in Filter, Search, LookUp, Sort, SortByColumns, and CountRows operations
Prompt
Provider
Anthropic (Claude) — no key
OpenAI (GPT) — no key
Google (Gemini) — no key
Re-run AI builder
Find all expressions that invoke `Filter`, `Search`, `LookUp`, `Sort`, `SortByColumns`, or `CountRows` against a data source. Inspect the arguments for **non-delegable constructs**: `Left`, `Right`, `Mid`, `Len`, `Find` as a predicate, `If` inside the predicate, arithmetic on columns, `User()` compared to a column, `Today()` inside the predicate instead of a pre-computed variable, `in` against an in-memory collection, or custom component function calls. Cross-check the referenced data source's type (SharePoint, SQL, Dataverse, Excel) against Microsoft's delegation matrix — hard-code the matrix as a lookup table inside the analyzer. Flag any call where the operator is unsupported for that source. Exclude cases where the first argument is a local collection or is declared via `ClearCollect`/`Collect` elsewhere in the code. Include the full invocation; the fix hint should name the specific non-delegable token detected.
Source Code
non-delegable-operations.js ·
Input data reference ↗
Checking…
Save JavaScript
export default { name: "Non-Delegable Operations", description: "Detects non-delegable constructs in Filter, Search, LookUp, Sort, SortByColumns, and CountRows operations", resultKey: "nonDelegableOps", resultSchema: { keys: [ { key: "name", label: "Function", suggestedFormat: "text" }, { key: "type", label: "Type", suggestedFormat: "badge-info" }, { key: "message", label: "Issue", suggestedFormat: "text" }, { key: "sourceType", label: "Data Source", suggestedFormat: "text-sm" }, { key: "nonDelegableConstruct", label: "Non-Delegable Construct", suggestedFormat: "code" }, { key: "fullExpression", label: "Full Expression", suggestedFormat: "truncate:120" }, { key: "locations", label: "Locations", suggestedFormat: "locations" }, { key: "confidence", label: "Confidence", suggestedFormat: "badge-confidence" } ] }, analyze(controlTree, refGraph, extraction) { const results = []; // Hard-coded delegation matrix based on Microsoft's documentation const delegationMatrix = { 'SharePoint': { operators: ['=', '<>', 'And', 'Or', 'Not', 'IsBlank', 'IsEmpty', 'StartsWith'], functions: ['Filter', 'Search', 'LookUp', 'Sort', 'SortByColumns', 'CountRows'], unsupportedFunctions: ['Left', 'Right', 'Mid', 'Len', 'Find', 'If', 'User', 'Today'] }, 'SQL Server': { operators: ['=', '<>', '<', '>', '<=', '>=', 'And', 'Or', 'Not', 'IsBlank', 'IsEmpty', 'StartsWith', 'EndsWith'], functions: ['Filter', 'Search', 'LookUp', 'Sort', 'SortByColumns', 'CountRows', 'Sum', 'Average', 'Min', 'Max'], unsupportedFunctions: ['Left', 'Right', 'Mid', 'Len', 'Find', 'If', 'User', 'Today'] }, 'Dataverse': { operators: ['=', '<>', '<', '>', '<=', '>=', 'And', 'Or', 'Not', 'IsBlank', 'IsEmpty', 'StartsWith', 'EndsWith'], functions: ['Filter', 'Search', 'LookUp', 'Sort', 'SortByColumns', 'CountRows', 'Sum', 'Average', 'Min', 'Max'], unsupportedFunctions: ['Left', 'Right', 'Mid', 'Len', 'Find', 'If', 'User', 'Today'] }, 'Excel': { operators: [], functions: [], unsupportedFunctions: ['Filter', 'Search', 'LookUp', 'Sort', 'SortByColumns', 'CountRows'] // Excel doesn't support delegation }, 'CSV': { operators: [], functions: [], unsupportedFunctions: ['Filter', 'Search', 'LookUp', 'Sort', 'SortByColumns', 'CountRows'] // CSV doesn't support delegation } }; // Track collections created with ClearCollect/Collect const localCollections = new Set(); // Find all ClearCollect and Collect calls to identify local collections for (const formula of extraction.allFormulas) { const clearCollectMatch = formula.formula.match(/ClearCollect\s*\(\s*([^,\s]+)/i); if (clearCollectMatch) { localCollections.add(clearCollectMatch[1]); } const collectMatch = formula.formula.match(/Collect\s*\(\s*([^,\s]+)/i); if (collectMatch) { localCollections.add(collectMatch[1]); } } // Pattern to match delegation functions const delegationFunctions = ['Filter', 'Search', 'LookUp', 'Sort', 'SortByColumns', 'CountRows']; const functionPattern = new RegExp(`\\b(${delegationFunctions.join('|')})\\s*\\(`, 'gi'); // Non-delegable constructs to detect const nonDelegablePatterns = [ { pattern: /\bLeft\s*\(/i, name: 'Left' }, { pattern: /\bRight\s*\(/i, name: 'Right' }, { pattern: /\bMid\s*\(/i, name: 'Mid' }, { pattern: /\bLen\s*\(/i, name: 'Len' }, { pattern: /\bFind\s*\(/i, name: 'Find' }, { pattern: /\bIf\s*\(/i, name: 'If' }, { pattern: /\bUser\s*\(\s*\)\s*[=<>]/i, name: 'User()' }, { pattern: /\bToday\s*\(\s*\)/i, name: 'Today()' }, { pattern: /[+\-*/]\s*\w+\.\w+/i, name: 'arithmetic on columns' }, { pattern: /\bin\s+\[/i, name: 'in operator with array' } ]; for (const formula of extraction.allFormulas) { let match; functionPattern.lastIndex = 0; while ((match = functionPattern.exec(formula.formula)) !== null) { const functionName = match[1]; const startPos = match.index; // Extract the full function call (simplified parsing) const fullExpression = extractFunctionCall(formula.formula, startPos); if (!fullExpression) continue; // Parse the first argument to determine the data source const firstArgMatch = fullExpression.match(/\(\s*([^,\)]+)/); if (!firstArgMatch) continue; const firstArg = firstArgMatch[1].trim(); // Skip if it's a local collection if (localCollections.has(firstArg)) continue; // Skip if it looks like a local collection (starts with lowercase or common collection patterns) if (/^[a-z]/.test(firstArg) || firstArg.startsWith('col') || firstArg.startsWith('temp')) { continue; } // Determine data source type (simplified heuristic) const sourceType = determineSourceType(firstArg); // Check for non-delegable constructs const nonDelegableConstructs = []; for (const pattern of nonDelegablePatterns) { if (pattern.pattern.test(fullExpression)) { nonDelegableConstructs.push(pattern.name); } } // Check if function is supported for this source type const matrix = delegationMatrix[sourceType]; if (matrix && !matrix.functions.includes(functionName)) { nonDelegableConstructs.push(`${functionName} function not supported`); } // Check for custom component function calls (simplified detection) const customFunctionMatch = fullExpression.match(/\b[A-Z][a-zA-Z0-9_]*\.[A-Z][a-zA-Z0-9_]*\s*\(/); if (customFunctionMatch) { nonDelegableConstructs.push('custom component function call'); } if (nonDelegableConstructs.length > 0) { results.push({ name: `${functionName}(${firstArg})`, type: "non-delegable-operation", message: `Non-delegable operation detected: ${nonDelegableConstructs.join(', ')}`, sourceType: sourceType, nonDelegableConstruct: nonDelegableConstructs.join(', '), fullExpression: fullExpression, locations: [{ control: formula.control, property: formula.property, file: formula.file, snippet: fullExpression }], confidence: nonDelegableConstructs.includes('If') || nonDelegableConstructs.includes('User()') || nonDelegableConstructs.includes('Today()') ? 'high' : 'medium' }); } } } return results; } }; // Helper function to extract full function call (simplified) function extractFunctionCall(formula, startPos) { let depth = 0; let i = startPos; let foundOpen = false; while (i < formula.length) { const char = formula[i]; if (char === '(') { depth++; foundOpen = true; } else if (char === ')') { depth--; if (depth === 0 && foundOpen) { return formula.substring(startPos, i + 1); } } i++; } return null; } // Helper function to determine data source type function determineSourceType(sourceName) { // Simplified heuristic based on naming patterns if (sourceName.includes('SharePoint') || sourceName.includes('SP_') || sourceName.endsWith('List')) { return 'SharePoint'; } if (sourceName.includes('SQL') || sourceName.includes('Database') || sourceName.includes('Table')) { return 'SQL Server'; } if (sourceName.includes('Dataverse') || sourceName.includes('CDS') || sourceName.includes('Common Data')) { return 'Dataverse'; } if (sourceName.includes('Excel') || sourceName.endsWith('.xlsx') || sourceName.endsWith('Workbook')) { return 'Excel'; } if (sourceName.includes('CSV') || sourceName.endsWith('.csv')) { return 'CSV'; } // Default assumption for cloud sources return 'SharePoint'; }
Report Output Columns
Columns shown in the generated report. Toggle off to hide; pick a declared key or use "Custom…" for dot-paths like
locations.0.file
.
+ Add column
Enabled
Key
Label
Format
Width
name
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
type
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
message
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
sourceType
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
nonDelegableConstruct
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
fullExpression
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
locations
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
confidence
Plain text
Small text
Muted grey text
Code snippet
Number (right-aligned)
Percentage (%)
Truncate at 80 chars
Truncate at 120 chars
Info pill (blue)
Confidence pill (red/yellow/grey)
Dead-code type pill
Bold name + copy button
Locations list
×
Cancel
Test analyzer
Save Columns
Running analyzer against solution…
This can take up to a minute for large solutions.
Test Non-Delegable Operations
×
Runs this analyzer in isolation against the chosen solution. Nothing is saved — the result is shown here only.
Use an existing solution
No solutions uploaded yet
Upload a solution file
Cancel
Run test
Test results
×