UNPKG

powerplatform-review-tool

Version:

Evaluate Power Platform solution zip files based on best practice patterns

628 lines (627 loc) 35.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.singleCollectCollections = exports.singleSetVariables = void 0; exports.patternCheckMediaFileSize = patternCheckMediaFileSize; exports.patternCheckUnusedMediaResources = patternCheckUnusedMediaResources; exports.patternCheckDelayLoading = patternCheckDelayLoading; exports.patternCheckAppSettings = patternCheckAppSettings; exports.patternCheckAppCheckerAccessibilityIssues = patternCheckAppCheckerAccessibilityIssues; exports.patternCheckUnusedElements = patternCheckUnusedElements; exports.patternCheckForNamedFormulas = patternCheckForNamedFormulas; exports.patternCheckForUxLayout = patternCheckForUxLayout; exports.patternCheckForCodeReadability = patternCheckForCodeReadability; exports.patternCheckForNestedAPICalls = patternCheckForNestedAPICalls; exports.patternCheckForErrorHandling = patternCheckForErrorHandling; exports.patternCheckForNPlusOneQuery = patternCheckForNPlusOneQuery; exports.patternCheckForInefficientDataRetrieval = patternCheckForInefficientDataRetrieval; exports.patternCheckForNestedFilters = patternCheckForNestedFilters; exports.patternCheckForPatchFormulaOptimization = patternCheckForPatchFormulaOptimization; const ManifestConstant_1 = require("../ManifestConstant"); const logger_1 = __importDefault(require("../utilities/logger")); const appHelper_1 = require("../utilities/appHelper"); async function patternCheckMediaFileSize(msApp) { const patternInfo = ManifestConstant_1.PowerAppsPatternDetails[ManifestConstant_1.PowerAppsPatternInfo.MediaFileSize]; let reviewResult = true; let recommendation = ""; let failureReason = ""; const assets = msApp.assets; if (assets.length > 0) { // Array to store assets exceeding the size threshold const exceedingAssets = []; // Check if any asset exceeds the threshold assets.forEach((asset) => { if (asset.exceedsThreshold) { reviewResult = false; exceedingAssets.push(asset.mediaName); } }); // Append the names of the files that exceed the threshold to recommendation message if (exceedingAssets.length > 0) { recommendation += patternInfo.recommendation + " " + exceedingAssets.join(", ") + ". "; failureReason += "Following files are exceeding 300KB file size: " + exceedingAssets.join(", ") + ". "; } } // Prepare the result object const result = { patternID: patternInfo.patternID, patternName: patternInfo.patternName, description: patternInfo.description, status: reviewResult ? "Pass" /* ManifestPropertyStatusNames.Pass */ : "Fail" /* ManifestPropertyStatusNames.Fail */, instanceValue: [], failureReason: !reviewResult ? failureReason : "", recommendation: patternInfo.recommendation, docLinks: patternInfo.docLinks, severity: patternInfo.severity, category: patternInfo.category, }; return result; } /** + * Checks if there are any "Unused Media Resources" issues reported by App Checker + * (i.e., ruleId === "app-UnsedMediaResources"). + * Dynamically appends the media resource name from the `fullyQualifiedName` (issue.Location). + */ async function patternCheckUnusedMediaResources(msApp) { // Retrieve the pattern info for Unused Media Resources const patternInfo = ManifestConstant_1.PowerAppsPatternDetails[ManifestConstant_1.PowerAppsPatternInfo.UnusedMediaResources]; let reviewResult = true; let failureReason = ""; let recommendation = ""; // Gather all issues where RuleId == "app-UnusedMediaResources" const unusedMediaIssues = msApp.appCheckerIssues.filter((issue) => issue.RuleId === "app-UnusedMediaResources"); if (unusedMediaIssues.length > 0) { reviewResult = false; // Collect the fullyQualifiedName for each issue const mediaNames = unusedMediaIssues.map((issue) => issue.Location); // Build a comma-separated string const allMediaNames = mediaNames.join(", "); // Dynamically add them to failureReason and recommendation failureReason = `The following media resources are unused: ${allMediaNames}.`; recommendation = `Remove or reference these unused media resources to optimize performance: ${allMediaNames}.`; } // Return the result return { patternID: patternInfo.patternID, patternName: patternInfo.patternName, description: patternInfo.description, status: reviewResult ? "Pass" /* ManifestPropertyStatusNames.Pass */ : "Fail" /* ManifestPropertyStatusNames.Fail */, instanceValue: [], failureReason: !reviewResult ? failureReason : "", recommendation: patternInfo.recommendation, docLinks: patternInfo.docLinks, severity: patternInfo.severity, category: patternInfo.category, }; } /* Checks if the app checker results contain any issue with ruleId == 'app-InefficientDelayLoading'. Dynamically appends the Location (fullyQualifiedName) in the failureReason and recommendation. */ async function patternCheckDelayLoading(msApp) { const patternInfo = ManifestConstant_1.PowerAppsPatternDetails[ManifestConstant_1.PowerAppsPatternInfo.InefficientDelayLoading]; let reviewResult = true; let failureReason = ""; let recommendation = ""; // Filter out all issues with 'app-InefficientDelayLoading' const delayLoadingIssues = msApp.appCheckerIssues.filter((issue) => issue.RuleId === "app-InefficientDelayLoading"); if (delayLoadingIssues.length > 0) { reviewResult = false; // Extract the fullyQualifiedName from each issue const locations = delayLoadingIssues.map((issue) => issue.Location); const joinedLocations = locations.join(", "); failureReason = `Following locations contain control references from other screens : ${joinedLocations}.`; recommendation = `Referencing controls from other screens can impact app performance by slowing down loading and navigation. Use variables, collections, and navigation context to share state across screens. Remove other screen control references from following locations: ${joinedLocations}.`; } return { patternID: patternInfo.patternID, patternName: patternInfo.patternName, description: patternInfo.description, status: reviewResult ? "Pass" /* ManifestPropertyStatusNames.Pass */ : "Fail" /* ManifestPropertyStatusNames.Fail */, instanceValue: [], failureReason: !reviewResult ? failureReason : "", recommendation: patternInfo.recommendation, docLinks: patternInfo.docLinks, severity: patternInfo.severity, category: patternInfo.category, }; } async function patternCheckAppSettings(msApp) { const patternInfo = ManifestConstant_1.PowerAppsPatternDetails[ManifestConstant_1.PowerAppsPatternInfo.AppSettings]; let reviewResult = true; let recommendation = ""; let failureReason = ""; // Extract the AppPreviewFlagsMap properties const appPreviewFlagsMap = msApp.appSettings?.AppPreviewFlagsMap || {}; // Array to store settings that are turned off const matchingKeysWithFalseValue = []; // Iterate through appPreviewFlagsMap and find keys in AppSettingsList that are false for (const key of ManifestConstant_1.AppSettingsList) { if (appPreviewFlagsMap[key] === false) { matchingKeysWithFalseValue.push(key); } } // If any settings are off, update reviewResult and recommendation if (matchingKeysWithFalseValue.length > 0) { reviewResult = false; recommendation = patternInfo.recommendation + ` ${matchingKeysWithFalseValue.join(", ")}.`; failureReason = `Following settings are turned off: ${matchingKeysWithFalseValue.join(", ")}.`; } // Prepare the object to return const result = { patternID: patternInfo.patternID, patternName: patternInfo.patternName, description: patternInfo.description, status: reviewResult ? "Pass" /* ManifestPropertyStatusNames.Pass */ : "Fail" /* ManifestPropertyStatusNames.Fail */, instanceValue: [], failureReason: !reviewResult ? failureReason : "", recommendation: patternInfo.recommendation, docLinks: patternInfo.docLinks, severity: patternInfo.severity, category: patternInfo.category, }; return result; } async function patternCheckAppCheckerAccessibilityIssues(msApp) { const patternInfo = ManifestConstant_1.PowerAppsPatternDetails[ManifestConstant_1.PowerAppsPatternInfo.AppCheckerAccessibilityIssues]; let reviewResult = true; let failureReason = "Errors or warnings from App Checker Results: " /* StringConstants.AppCheckerErrorStatement */; const distinctRuleIds = new Set(); // Iterate through msApp.appCheckerIssues array and collect distinct ruleIds where category is "accessibility" msApp.appCheckerIssues.forEach((issue) => { if (issue.Category === "accessibility") { distinctRuleIds.add(issue.RuleId); } }); // If there are accessibility issues, update the reviewResult and failureReason if (distinctRuleIds.size > 0) { reviewResult = false; failureReason = failureReason.concat(" ", Array.from(distinctRuleIds).join(", ")); } // Prepare the result object const result = { patternID: patternInfo.patternID, patternName: patternInfo.patternName, description: patternInfo.description, status: reviewResult ? "Pass" /* ManifestPropertyStatusNames.Pass */ : "Fail" /* ManifestPropertyStatusNames.Fail */, instanceValue: [], failureReason: !reviewResult ? failureReason : "", recommendation: patternInfo.recommendation, docLinks: patternInfo.docLinks, severity: patternInfo.severity, category: patternInfo.category, }; return result; } async function patternCheckUnusedElements(msApp) { const patternInfo = ManifestConstant_1.PowerAppsPatternDetails[ManifestConstant_1.PowerAppsPatternInfo.UnusedElements]; let reviewResult = true; let failureReason = "Errors or warnings from App Checker Results: " /* StringConstants.AppCheckerErrorStatement */; let recommendation = ""; const foundRuleIds = new Set(); const locations = []; // To store fullyQualifiedName from issues // Iterate through msApp.appCheckerIssues array to find relevant RuleIds msApp.appCheckerIssues.forEach((issue) => { if (ManifestConstant_1.RelevantRuleIdsForUnusedElements.includes(issue.RuleId)) { foundRuleIds.add(issue.RuleId); // Also store the location for each unused variable issue locations.push(issue.Location); } }); // If any relevant RuleIds are found, update reviewResult and failureReason if (foundRuleIds.size > 0) { reviewResult = false; failureReason = failureReason.concat(" " + Array.from(foundRuleIds).join(", ")); } // Now append the fullyQualifiedName values of these UnusedVariables to both failureReason and recommendation if (locations.length > 0) { const locationList = locations.join(", "); failureReason += ` The following variables are unused: ${locationList}.`; recommendation = `Remove or reference these unused variables: ${locationList}.`; } // Prepare the result object const result = { patternID: patternInfo.patternID, patternName: patternInfo.patternName, description: patternInfo.description, status: reviewResult ? "Pass" /* ManifestPropertyStatusNames.Pass */ : "Fail" /* ManifestPropertyStatusNames.Fail */, instanceValue: [], failureReason: !reviewResult ? failureReason : "", recommendation: patternInfo.recommendation, docLinks: patternInfo.docLinks, severity: patternInfo.severity, category: patternInfo.category, }; return result; } const setRegex = /\bSet\s*\(\s*([A-Za-z0-9_]+)\s*,/gi; const collectRegex = /\b(?:Collect|ClearCollect)\s*\(\s*([A-Za-z0-9_]+)\s*,/gi; const patchRegex = /\bPatch\s*\(\s*([A-Za-z0-9_]+)\s*,/gi; // Track single-use variables/collections here so we can finalize them later: exports.singleSetVariables = {}; exports.singleCollectCollections = {}; // We'll store the *second+ occurrence* or patch usage in these sets so that // we know to exclude them from "never-updated" lists. const updatedVariables = new Set(); const updatedCollections = new Set(); /** * patternCheckForNamedFormulas * * Called from `processControlProperties` for each property formula. * We track single occurrence of `Set(X,...)` or `Collect(Y,...)` / `ClearCollect(Y,...)` * plus see if there's any `Patch(Y,...)` to mark a collection as updated. * * We do NOT immediately push to `failures` here, because we only know an item is * "never-updated" if we don't see a second occurrence or patch. Instead, we store * the initial occurrence data in global maps. */ // eslint-disable-next-line sonarjs/cognitive-complexity function patternCheckForNamedFormulas(propertyValue, screenName, controlName, propertyName, controlType) { if (!propertyValue || typeof propertyValue !== "string") return; const code = (0, appHelper_1.removeComments)(propertyValue); let match; /////////////////////////////////////////////////// // A) Look for Set(X, ...) /////////////////////////////////////////////////// while ((match = setRegex.exec(code)) !== null) { const varName = match[1]; // Get full Set(...) expression const startIdx = match.index + match[0].indexOf("("); const { endIndex } = (0, appHelper_1.findMatchingParenthesis)(code, startIdx); const fullExpression = endIndex !== -1 ? code.substring(match.index, endIndex + 1) : match[0]; // If we've already marked it updated or used multiple times, do nothing if (updatedVariables.has(varName)) { continue; } if (!exports.singleSetVariables[varName]) { // first occurrence => store a FailureInfo in singleSetVariables exports.singleSetVariables[varName] = { ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: code, PatternName: ManifestConstant_1.PowerAppsPatternInfo.NamedFormulas, FailureReason: "", ControlType: controlType, FailedPowerFxCode: fullExpression, // Captures full Set(...) snippet }; } else { // second occurrence => remove from singleSetVariables => mark updated delete exports.singleSetVariables[varName]; updatedVariables.add(varName); } } /////////////////////////////////////////////////// // B) Look for Collect(Y, ...) or ClearCollect(Y, ...) /////////////////////////////////////////////////// while ((match = collectRegex.exec(code)) !== null) { const colName = match[1]; // Get full Collect(...) or ClearCollect(...) expression const startIdx = match.index + match[0].indexOf("("); const { endIndex } = (0, appHelper_1.findMatchingParenthesis)(code, startIdx); const fullExpression = endIndex !== -1 ? code.substring(match.index, endIndex + 1) : match[0]; if (updatedCollections.has(colName)) { continue; } if (!exports.singleCollectCollections[colName]) { exports.singleCollectCollections[colName] = { ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: code, PatternName: ManifestConstant_1.PowerAppsPatternInfo.NamedFormulas, FailureReason: "", ControlType: controlType, FailedPowerFxCode: fullExpression, // Captures full Collect(...) snippet }; } else { // second time => remove from singleCollectCollections => mark updated delete exports.singleCollectCollections[colName]; updatedCollections.add(colName); } } /////////////////////////////////////////////////// // C) Look for Patch(Y, ...) /////////////////////////////////////////////////// while ((match = patchRegex.exec(code)) !== null) { const colName = match[1]; if (exports.singleCollectCollections[colName]) { delete exports.singleCollectCollections[colName]; } updatedCollections.add(colName); } } function patternCheckForUxLayout(screenName, firstChildControl, failures) { try { const childName = Object.keys(firstChildControl)[0]; const childControl = firstChildControl[childName]; if (!childControl.Control.startsWith("GroupContainer")) { failures.push({ ScreenName: screenName, ControlName: "", PropertyName: "", Code: "", PatternName: ManifestConstant_1.PowerAppsPatternInfo.UxLayout, FailureReason: "Use Containers to build responsive apps.", }); } } catch (error) { if (error instanceof Error) { logger_1.default.error(`Error processing pattern: ${error.message}`); } else { logger_1.default.error("Unknown error processing pattern"); } } } function patternCheckForCodeReadability(propertyName, propertyValue, failures, screenName, controlName, controlType) { const codeWithComments = propertyValue; const codeWithoutComments = (0, appHelper_1.removeComments)(propertyValue); if (typeof codeWithoutComments === "string") { // Check if code has no comments and length exceeds 1000 characters if (codeWithComments.length === codeWithoutComments.length && codeWithoutComments.length > 1000) { failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: codeWithoutComments, PatternName: ManifestConstant_1.PowerAppsPatternInfo.CodeReadability, FailureReason: "Code length exceeds 1000 characters without any comments.", ControlType: controlType, FailedPowerFxCode: codeWithoutComments, }); } // Check for nested If functions const nestedIfExpression = (0, appHelper_1.containsNestedIf)(codeWithoutComments); if (nestedIfExpression) { // console.warn("PatternCheckForCodeReadability"); // console.warn("Code\n", codeWithoutComments); // console.warn("Nested If expression\n", nestedIfExpression); failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: codeWithoutComments, PatternName: ManifestConstant_1.PowerAppsPatternInfo.CodeReadability, FailureReason: "Nested If statements detected.", ControlType: controlType, FailedPowerFxCode: nestedIfExpression, // Store only the nested failing If(...) snippet }); } } } // eslint-disable-next-line sonarjs/cognitive-complexity function patternCheckForNestedAPICalls(propertyName, propertyValue, failures, screenName, controlName, msapp, externalDataSources, controlType) { const cleanCode = (0, appHelper_1.removeComments)(propertyValue); if (typeof cleanCode === "string") { // Find all occurrences of Filter/Search/ForAll/LookUp functions const outerOccurrences = (0, appHelper_1.findAPIFunctionOccurrences)(cleanCode); for (const outerOccurrence of outerOccurrences) { // For each outer occurrence, check if there is a nested Patch/Filter/Search/ForAll/LookUp function inside const content = outerOccurrence.content; const innerOccurrences = (0, appHelper_1.findAPIFunctionOccurrences)(content); for (const innerOccurrence of innerOccurrences) { // Get the first parameter of the inner function const firstParam = (0, appHelper_1.getFirstParameter)(innerOccurrence.content); const dataSourceName = (0, appHelper_1.normalizeDataSourceName)(firstParam); if (externalDataSources.includes(dataSourceName.toLowerCase()) || (dataSourceName.toLowerCase().includes("office365users") && externalDataSources.includes("office365users"))) { // console.warn("PatternCheckForNestedAPICalls"); // console.warn("OuterContent\n", outerOccurrence.functionName + "(" +outerOccurrence.content + ")"); // console.warn("InnerContent\n", innerOccurrence.functionName + "("+ innerOccurrence.content + ")"); // console.warn("Code\n", cleanCode); failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: cleanCode, PatternName: ManifestConstant_1.PowerAppsPatternInfo.NestedAPICalls, ControlType: controlType, // New property holding only the failing portion of the code FailedPowerFxCode: innerOccurrence.functionName + "(" + innerOccurrence.content + ")", FailureReason: "Filter/Search/ForAll/LookUp/Patch function with external API call detected inside another Filter/Search/ForAll/LookUp/Patch function.", }); // Break after finding the first failure in this code segment break; } } } } } // eslint-disable-next-line sonarjs/cognitive-complexity function patternCheckForErrorHandling(propertyName, propertyValue, failures, screenName, controlName, msapp, externalDataSources, controlType) { const cleanCode = (0, appHelper_1.removeComments)(propertyValue); if (typeof cleanCode === "string") { // Find all occurrences of Patch operations const patchOccurrences = (0, appHelper_1.findPatchOccurrences)(cleanCode); for (const patchOccurrence of patchOccurrences) { // Extract the first argument of Patch const firstArgument = (0, appHelper_1.getNthArgument)(patchOccurrence.content, 1); if (firstArgument) { const dataSourceName = (0, appHelper_1.normalizeDataSourceName)(firstArgument); if (externalDataSources.includes(dataSourceName.toLowerCase()) || (dataSourceName.toLowerCase().includes("office365users") && externalDataSources.includes("office365users"))) { // Check if this Patch is wrapped inside IfError or IsError const isWrapped = (0, appHelper_1.isFunctionWrapped)(cleanCode, patchOccurrence.startIndex - 1, ["IfError", "IsError"]); if (!isWrapped) { // console.warn("PatternCheckForErrorHandling"); // console.warn("PatchContent\n", patchOccurrence.functionName + "(" +patchOccurrence.content + ")"); // console.warn("Code\n", cleanCode); failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: cleanCode, PatternName: ManifestConstant_1.PowerAppsPatternInfo.ErrorHandling, ControlType: controlType, FailedPowerFxCode: patchOccurrence.functionName + "(" + patchOccurrence.content + ")", FailureReason: "Patch function with external API call detected without error handling with IfError/IsError function.", }); } } } } } } // eslint-disable-next-line sonarjs/cognitive-complexity function patternCheckForNPlusOneQuery(propertyName, propertyValue, failures, screenName, controlName, msapp, externalDataSources, controlType) { const cleanCode = (0, appHelper_1.removeComments)(propertyValue); if (typeof propertyValue === "string") { for (const func of ManifestConstant_1.QueryFunctions) { const regex = new RegExp(`${func}\\(([^)]+)\\)`, "gi"); let match; while ((match = regex.exec(cleanCode)) !== null) { const args = match[1]; const dataSourceName = (0, appHelper_1.normalizeDataSourceName)(args.split(",")[0].trim()); if (externalDataSources.includes(dataSourceName.toLowerCase()) || (dataSourceName.toLowerCase().includes("office365users") && externalDataSources.includes("office365users"))) { // console.warn("PatternCheckForNPlusOneQuery"); // console.warn("Code\n", cleanCode); // console.warn("Match\n", match[0]); failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: cleanCode, PatternName: ManifestConstant_1.PowerAppsPatternInfo.NPlusOne, ControlType: controlType, FailedPowerFxCode: match[0], FailureReason: "Filter/LookUp/Search/CountRows/ForAll/Patch function with external API call detected inside a Gallery child control.", }); } } } } } // eslint-disable-next-line sonarjs/cognitive-complexity function patternCheckForInefficientDataRetrieval(propertyName, propertyValue, failures, screenName, controlName, msapp, externalDataSources, controlType) { const cleanCode = (0, appHelper_1.removeComments)(propertyValue); if (typeof cleanCode === "string") { const occurrences = (0, appHelper_1.findFirstLastOccurrences)(cleanCode); for (const occurrence of occurrences) { // For each occurrence of First/Last, check if content contains Filter or Search const content = occurrence.content; const innerOccurrences = (0, appHelper_1.findFilterSearchOccurrences)(content); for (const innerOccurrence of innerOccurrences) { // Get the first parameter of Filter or Search const firstParam = (0, appHelper_1.getFirstParameter)(innerOccurrence.content); const dataSourceName = (0, appHelper_1.normalizeDataSourceName)(firstParam); if (externalDataSources.includes(dataSourceName.toLowerCase()) || (dataSourceName.toLowerCase().includes("office365users") && externalDataSources.includes("office365users"))) { // console.warn("PatternCheckForInefficientDataRetrieval"); // console.warn("OuterContent\n", occurrence.functionName + "(" +occurrence.content + ")"); // console.warn("InnerContent\n", innerOccurrence.functionName + "("+ innerOccurrence.content + ")"); // console.warn("Code\n", cleanCode); failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: cleanCode, PatternName: ManifestConstant_1.PowerAppsPatternInfo.InefficientDataRetrieval, ControlType: controlType, FailedPowerFxCode: innerOccurrence.functionName + "(" + innerOccurrence.content + ")", FailureReason: "Filter/Search function with external API call detected inside First/Last function.", }); } } } } } // eslint-disable-next-line sonarjs/cognitive-complexity function patternCheckForNestedFilters(propertyName, propertyValue, failures, screenName, controlName, msapp, externalDataSources, controlType) { const cleanCode = (0, appHelper_1.removeComments)(propertyValue); if (typeof cleanCode === "string") { // Find all occurrences of Filter/Search functions const outerOccurrences = (0, appHelper_1.findFilterSearchOccurrences)(cleanCode); for (const outerOccurrence of outerOccurrences) { // For each outer occurrence, check if there is a nested Filter/Search/LookUp inside const content = outerOccurrence.content; const innerOccurrences = (0, appHelper_1.findFilterSearchLookUpOccurrences)(content); for (const innerOccurrence of innerOccurrences) { // Get the first parameter of inner function const firstParam = (0, appHelper_1.getFirstParameter)(innerOccurrence.content); const dataSourceName = (0, appHelper_1.normalizeDataSourceName)(firstParam); if (externalDataSources.includes(dataSourceName.toLowerCase()) || (dataSourceName.toLowerCase().includes("office365users") && externalDataSources.includes("office365users"))) { //console.warn("NestedFilter"); // console.warn("OuterContent\n", outerOccurrence.functionName + "(" +outerOccurrence.content + ")"); // console.warn("InnerContent\n", innerOccurrence.functionName + "("+ innerOccurrence.content + ")"); // console.warn("Code:\n", cleanCode); failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: cleanCode, PatternName: ManifestConstant_1.PowerAppsPatternInfo.NestedFilters, ControlType: controlType, FailedPowerFxCode: innerOccurrence.functionName + "(" + innerOccurrence.content + ")", FailureReason: "Nested Filter/Search/LookUp function with external API call detected inside Filter/Search function.", }); // if (cleanCode.indexOf(innerOccurrence.functionName + "(" + innerOccurrence.content + ")") !== -1) { // console.warn("Found exact match in Code. Pushing to failures."); // console.warn("PassedPfx\n", innerOccurrence.functionName + "(" + innerOccurrence.content + ")"); // console.warn("Code\n", cleanCode); // //failures.push({ Code: cleanCode, FailedPowerFxCode, ... }); // } else { // logger.warn(`Mismatch: FailedPowerFxCode not found in Code. Skipping...`); // console.warn("FailedPFx\n", innerOccurrence.functionName + "(" + innerOccurrence.content + ")"); // console.warn("Code\n", cleanCode); // } // if (cleanCode.indexOf(outerOccurrence.functionName + "(" + outerOccurrence.content + ")") !== -1) { // console.warn("Found exact match in Code. Pushing to failures."); // console.warn("PassedPfx\n", outerOccurrence.functionName + "(" + outerOccurrence.content + ")"); // console.warn("Code\n", cleanCode); // //failures.push({ Code: cleanCode, FailedPowerFxCode, ... }); // } else { // logger.warn(`Mismatch: FailedPowerFxCode not found in Code. Skipping...`); // console.warn("FailedPFx\n", outerOccurrence.functionName + "(" + outerOccurrence.content + ")"); // console.warn("Code\n", cleanCode); // } } } } } } // eslint-disable-next-line sonarjs/cognitive-complexity function patternCheckForPatchFormulaOptimization(propertyName, propertyValue, failures, screenName, controlName, msapp, externalDataSources, controlType) { const cleanCode = (0, appHelper_1.removeComments)(propertyValue); if (typeof cleanCode === "string") { const patchOccurrences = (0, appHelper_1.findPatchOccurrences)(cleanCode); for (const patchOccurrence of patchOccurrences) { // Extract the second argument of Patch const secondArgument = (0, appHelper_1.getNthArgument)(patchOccurrence.content, 2); if (secondArgument) { // Check if the second argument contains Filter/Search/LookUp const innerOccurrences = (0, appHelper_1.findFilterSearchLookUpOccurrences)(secondArgument); for (const innerOccurrence of innerOccurrences) { // Get the first parameter of inner function const firstParam = (0, appHelper_1.getFirstParameter)(innerOccurrence.content); const dataSourceName = (0, appHelper_1.normalizeDataSourceName)(firstParam); if (externalDataSources.includes(dataSourceName.toLowerCase()) || (dataSourceName.toLowerCase().includes("office365users") && externalDataSources.includes("office365users"))) { //console.warn("PatternCheckForPatchFormulaOptimization"); //console.warn("OuterContent\n", patchOccurrence.functionName + "(" +patchOccurrence.content + ")"); //console.warn("InnerContent\n", innerOccurrence.functionName + "("+ innerOccurrence.content + ")"); //console.warn("Code\n", cleanCode); failures.push({ ScreenName: screenName, ControlName: controlName ? controlName : screenName.replace(/ /g, "") + "Properties", PropertyName: propertyName, Code: cleanCode, PatternName: ManifestConstant_1.PowerAppsPatternInfo.PatchFormulaOptimization, ControlType: controlType, FailedPowerFxCode: innerOccurrence.functionName + "(" + innerOccurrence.content + ")", FailureReason: "Filter/Search/LookUp function with external API call detected inside Patch function.", }); } } } } } }