Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix loop handling in compiler #65

Merged
merged 16 commits into from
Mar 7, 2024
Binary file added .yarn/install-state.gz
Binary file not shown.
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lightning-flow-scanner-core",
"version": "2.27.0",
"version": "2.28.0",
"main": "out/**",
"types": "out/index.d.ts",
"scripts": {
Expand All @@ -26,5 +26,6 @@
"devDependencies": {
"@types/jsforce": "^1.11.0",
"@types/mocha": "^9.0.0"
}
},
"packageManager": "[email protected]"
}
11 changes: 9 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IRuleDefinition } from './main/interfaces/IRuleDefinition';
import IRuleDefinition from './main/interfaces/IRuleDefinition';
import { IRulesConfig } from './main/interfaces/IRulesConfig';
import { FixFlows } from './main/libs/FixFlows';
import { GetRuleDefinitions } from './main/libs/GetRuleDefinitions';
Expand Down Expand Up @@ -56,6 +56,13 @@ export function fix(flows: Flow[]): ScanResult[] {
}

export { default as Flow } from './main/models/Flow';
export { default as FlowAttribute } from './main/models/FlowAttribute';
export { default as FlowElement } from './main/models/FlowElement';
export { default as FlowNode } from './main/models/FlowNode';
export { default as FlowType } from './main/models/FlowType';
export { default as FlowVariable } from './main/models/FlowVariable';
export { default as Compiler } from './main/libs/Compiler';
export { default as ScanResult } from './main/models/ScanResult';
export { default as RuleResult } from './main/models/RuleResult';
export { default as ResultDetails } from './main/models/ResultDetails';
export { default as ResultDetails } from './main/models/ResultDetails';
export { default as IRuleDefinition } from './main/interfaces/IRuleDefinition';
3 changes: 2 additions & 1 deletion src/main/interfaces/IRuleConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface IRuleConfig {
severity: string;
severity?: string;
path?: string;
}
4 changes: 2 additions & 2 deletions src/main/interfaces/IRuleDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import Flow from '../models/Flow';
import RuleResult from '../models/RuleResult';

export interface IRuleDefinition {
uri: string;
export default interface IRuleDefinition {
name: string;
label: string;
description: string;
supportedTypes: string[];
type: string;
docRefs: { label: string, path: string }[];
isConfigurable: boolean;
uri?: string;
severity?: string;

execute(flow: Flow, ruleOptions?: {}): RuleResult;
Expand Down
43 changes: 14 additions & 29 deletions src/main/libs/Compiler.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,40 @@
import Flow from '../models/Flow';
import { FlowNode } from '../models/FlowNode';
import FlowNode from '../models/FlowNode';

export class Compiler {
private visitedElements: Set<string>;
export default class Compiler {
public visitedElements: Set<string>;

constructor() {
this.visitedElements = new Set<string>();
}

isInLoop = (flow: Flow, element: FlowNode, startOfLoop: FlowNode): boolean => {
const connectors = element.connectors || [];
for (const connector of connectors) {
if (connector.reference) {
const referencedElement = (flow.elements as FlowNode[]).find(el => el.name === connector.reference);
if (referencedElement === startOfLoop) {
return true;
}
if (this.isInLoop(flow, referencedElement, startOfLoop)) {
return true;
}
}
}
return false;
};

traverseFlow(flow: Flow, startElementName: string, visitCallback: (element: FlowNode) => void) {
traverseFlow(flow: Flow, startElementName: string, visitCallback: (element: FlowNode) => void, endElementName?: string) {
// Iterative Deepening Depth-First Search (IDDFS)
// let depth = 0;
let elementsToVisit = [startElementName];

while (elementsToVisit.length > 0) {
const nextElements = [];

for (const elementName of elementsToVisit) {
if (!this.visitedElements.has(elementName)) {
const currentElement = flow.elements.find(element => element instanceof FlowNode && element.name === elementName) as FlowNode;
if (currentElement) {
visitCallback(currentElement);
this.visitedElements.add(elementName);
nextElements.push(...this.findNextElements(flow, currentElement));
nextElements.push(...this.findNextElements(flow, currentElement, endElementName));
}
}
}


if (nextElements.length === 0) { // If no more next elements
break; // Terminate the traversal
}

elementsToVisit = nextElements;
// add logic to control depth or terminate the traversal based on requirements.
// depth++;
}
}

private findNextElements(flow: Flow, currentElement: FlowNode): string[] {
private findNextElements(flow: Flow, currentElement: FlowNode, endElementName?: string): string[] {
const nextElements: string[] = [];

if (currentElement.connectors && currentElement.connectors.length > 0) {
Expand All @@ -59,7 +44,7 @@ export class Compiler {
const nextElement = flow.elements.find(
element => element instanceof FlowNode && element.name === connector.reference
);
if (nextElement instanceof FlowNode) {
if (nextElement instanceof FlowNode && nextElement.name !== endElementName) {
nextElements.push(nextElement.name);
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/main/libs/FixFlows.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { FlowNode } from '../models/FlowNode';
import { FlowVariable } from '../models/FlowVariable';
import { UnconnectedElement } from '../rules/UnconnectedElement';
import { UnusedVariable } from '../rules/UnusedVariable';
import { BuildFlow } from './BuildFlow';
Expand All @@ -17,13 +15,13 @@ export function FixFlows(flows: core.Flow[]): core.ScanResult[] {
const nodesToBuild = flow.elements.filter(node => {
switch (node.metaType) {
case 'variable':
const nodeVar = node as FlowVariable;
const nodeVar = node as core.FlowVariable;
if (!unusedVariableReferences.includes(nodeVar.name)) {
return node;
}
break;
case 'node':
const nodeElement = node as FlowNode;
const nodeElement = node as core.FlowNode;
if (!unconnectedElementsReferences.includes(nodeElement.name)) {
return node;
}
Expand Down
36 changes: 22 additions & 14 deletions src/main/libs/GetRuleDefinitions.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { IRuleDefinition } from '../interfaces/IRuleDefinition';
import IRuleDefinition from '../interfaces/IRuleDefinition';
import { DefaultRuleStore } from '../store/DefaultRuleStore';
import { DynamicRule } from './DynamicRule';
import { RuleLoader } from './RuleLoader';

export function GetRuleDefinitions(ruleConfig?: Map<string, {}>): IRuleDefinition[] {
const matchedRules: any[] = [];
let severity = 'error';

const selectedRules: any[] = [];
if (ruleConfig && ruleConfig instanceof Map) {
for (const ruleName of ruleConfig.keys()) {
try{
const matchedRule = new DynamicRule(ruleName);
const configuredSeverity = ruleConfig.get(ruleName)['severity'];
if (configuredSeverity && (configuredSeverity === "error" || configuredSeverity === "warning" || configuredSeverity === "note")) {
severity = configuredSeverity;
}
let severity = 'error';
try {
const configuredPath = ruleConfig.get(ruleName)['path']
const configuredSeverity = ruleConfig.get(ruleName)['severity'];
if (configuredSeverity && (configuredSeverity === "error" || configuredSeverity === "warning" || configuredSeverity === "note")) {
severity = configuredSeverity;
}
if(configuredPath){

let customRule = RuleLoader.loadCustomRule(configuredPath);
selectedRules['severity'] = severity;
selectedRules.push(customRule);
} else {
const matchedRule = new DynamicRule(ruleName);
matchedRule['severity'] = severity;
matchedRules.push(matchedRule);
selectedRules.push(matchedRule);
}
} catch (error) {
console.log(error.message)
}
Expand All @@ -24,10 +32,10 @@ export function GetRuleDefinitions(ruleConfig?: Map<string, {}>): IRuleDefinitio
// tslint:disable-next-line:forin
for (const rule in DefaultRuleStore) {
const matchedRule = new DynamicRule(rule);
matchedRule['severity'] = severity;
matchedRules.push(matchedRule);
matchedRule['severity'] = 'error';
selectedRules.push(matchedRule);
}
}

return matchedRules;
return selectedRules;
}
13 changes: 13 additions & 0 deletions src/main/libs/RuleLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import IRuleDefinition from "../interfaces/IRuleDefinition";
import { RuleParser } from "./RuleParser";

export class RuleLoader {
static loadCustomRule(filePath: string): IRuleDefinition | undefined {

// todo verify attributes & method
// return RuleParser.parseRuleFile(filePath);
const externalRule: any = require(filePath);
const customRuleInstance = new externalRule() as IRuleDefinition;
return customRuleInstance;
}
}
55 changes: 55 additions & 0 deletions src/main/libs/RuleParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as ts from 'typescript';
import * as fs from 'fs';
import IRuleDefinition from '../interfaces/IRuleDefinition';

export class RuleParser {
static parseRuleFile(filePath: string): IRuleDefinition | undefined {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const sourceFile = ts.createSourceFile(
filePath,
fileContent,
ts.ScriptTarget.ESNext
);

let ruleDefinition: IRuleDefinition | undefined;

function visit(node: ts.Node) {
if (ts.isClassDeclaration(node)) {
const className = node.name?.escapedText;
if (className) {
const methods = node.members.filter(member =>
ts.isMethodDeclaration(member)
) as ts.MethodDeclaration[];
const requiredMethods = ['execute'];
const methodNames = methods.map(method => method.name?.['escapedText']);
const implementsMethods = requiredMethods.every(methodName =>
methods.some(method =>
methodNames.includes(methodName)
)
);
const properties = node.members.filter(member =>
ts.isPropertyDeclaration(member)
) as ts.PropertyDeclaration[];
const requiredProperties = ['name', 'label', 'description', 'supportedTypes', 'type', 'docRefs', 'isConfigurable'];
const propertyNames = properties.map(property => property.name?.['escapedText']);
const implementsProperties = requiredProperties.every(propertyName =>
propertyNames.includes(propertyName)
);
if (implementsMethods) {
ruleDefinition = RuleParser.extractRuleDefinition(node);
}
}
}
ts.forEachChild(node, visit);
}

ts.forEachChild(sourceFile, visit);
return ruleDefinition;
}

private static extractRuleDefinition(classDeclaration: ts.ClassDeclaration): IRuleDefinition {
// Extract relevant information from the class declaration
// and create an instance of IRuleDefinition
return null;
}
}
3 changes: 1 addition & 2 deletions src/main/libs/ScanFlows.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { IRuleDefinition } from '../interfaces/IRuleDefinition';
import { GetRuleDefinitions } from './GetRuleDefinitions';
import { keys } from './Keys';
import * as core from '../../index';

export function ScanFlows(flows: core.Flow[], rulesConfig?: Map<string, {}>): core.ScanResult[] {

const flowResults: core.ScanResult[] = [];
let selectedRules: IRuleDefinition[] = [];
let selectedRules: core.IRuleDefinition[] = [];
if (rulesConfig) {
selectedRules = GetRuleDefinitions(rulesConfig);
} else {
Expand Down
6 changes: 3 additions & 3 deletions src/main/models/Flow.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FlowNode } from './FlowNode';
import FlowNode from './FlowNode';
import { FlowMetadata } from './FlowMetadata';
import { FlowElement } from './FlowElement';
import { FlowVariable } from './FlowVariable';
import FlowElement from './FlowElement';
import FlowVariable from './FlowVariable';
import * as p from 'path';

export default class Flow {
Expand Down
2 changes: 1 addition & 1 deletion src/main/models/FlowAttribute.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class FlowAttribute {
export default class FlowAttribute {

public name: string;
public subtype: string;
Expand Down
2 changes: 1 addition & 1 deletion src/main/models/FlowElement.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class FlowElement{
export default class FlowElement{

public subtype:string;
public metaType:string;
Expand Down
2 changes: 1 addition & 1 deletion src/main/models/FlowMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {FlowElement} from './FlowElement';
import FlowElement from './FlowElement';

export class FlowMetadata extends FlowElement{

Expand Down
4 changes: 2 additions & 2 deletions src/main/models/FlowNode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FlowElementConnector } from './FlowElementConnector';
import { FlowElement } from './FlowElement';
import FlowElement from './FlowElement';

export class FlowNode extends FlowElement {
export default class FlowNode extends FlowElement {

public connectors: FlowElementConnector[] = [];
public name: string;
Expand Down
2 changes: 1 addition & 1 deletion src/main/models/FlowType.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class FlowType {
export default class FlowType {

public static backEndTypes = ['AutoLaunchedFlow', 'CustomEvent', 'InvocableProcess', 'Orchestrator', 'EvaluationFlow', 'ActionCadenceAutolaunchedFlow'];
public static processBuilder = ['Workflow'];
Expand Down
4 changes: 2 additions & 2 deletions src/main/models/FlowVariable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FlowElement } from './FlowElement';
import FlowElement from './FlowElement';

export class FlowVariable extends FlowElement {
export default class FlowVariable extends FlowElement {

public name: string;
public dataType: string;
Expand Down
6 changes: 3 additions & 3 deletions src/main/models/ResultDetails.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FlowAttribute } from "./FlowAttribute";
import { FlowNode } from "./FlowNode";
import { FlowVariable } from "./FlowVariable";
import FlowAttribute from "./FlowAttribute";
import FlowNode from "./FlowNode";
import FlowVariable from "./FlowVariable";

export default class ResultDetails {

Expand Down
2 changes: 1 addition & 1 deletion src/main/models/RuleCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class RuleCommon {
this.supportedTypes = info.supportedTypes;
this.label = info.label;
this.description = info.description;
this.uri = 'https://github.com/Force-Config-Control/lightning-flow-scanner-core/tree/master/src/main/rules/' + info.name + '.ts';
this.uri = 'https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core/tree/master/src/main/rules/' + info.name + '.ts';
this.docRefs = info.docRefs;
this.isConfigurable = info.isConfigurable;
this.severity = (optional && optional.severity) ? optional.severity : 'error';
Expand Down
2 changes: 1 addition & 1 deletion src/main/models/RuleResult.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IRuleDefinition } from '../interfaces/IRuleDefinition';
import IRuleDefinition from '../interfaces/IRuleDefinition';
import ResultDetails from './ResultDetails';

export default class RuleResult {
Expand Down
Loading
Loading