All files / src/rules functional_writing.ts

100% Statements 134/134
91.67% Branches 33/36
100% Functions 9/9
100% Lines 134/134

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 1341x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 15306x 15306x 15306x 15306x 1x 7654x 7654x 7654x 7654x 7654x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 7654x 7654x 8x 8x 7654x 7654x 7346x 7346x 7654x 7654x 139x 139x 7654x 7654x 164x 164x 164x 164x 10x 10x 154x 154x 164x 34x 34x 154x 154x 154x 164x 812x 812x 812x 812x 1x 812x 152x 811x 17x 6x 6x 11x 17x 17x 3x 3x 8x 8x 812x 154x 154x 154x 7654x 7654x 8x 8x 8x 8x 7654x 7654x 8x 8x 8x 8x 8x 8x 7x 7x 4x 4x 7x 3x 3x 3x 3x 7x 1x 1x 1x 1x 8x 8x 7654x 7654x
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import * as Statements from "../abap/2_statements/statements";
import * as Expressions from "../abap/2_statements/expressions";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Class} from "../objects";
import {InfoClassDefinition} from "../abap/4_file_information/_abap_file_information";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPObject} from "../objects/_abap_object";
import {DDIC} from "../ddic";
import {EditHelper} from "../edit_helper";
import {StatementNode} from "../abap/nodes/statement_node";
import {ABAPFile} from "../abap/abap_file";
 
export class FunctionalWritingConf extends BasicRuleConfig {
  /** Ignore functional writing in exception classes, local + global */
  public ignoreExceptions: boolean = true;
}
 
export class FunctionalWriting extends ABAPRule {
 
  private conf = new FunctionalWritingConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "functional_writing",
      title: "Use functional writing",
      shortDescription: `Detects usage of call method when functional style calls can be used.`,
      extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-functional-to-procedural-calls
https://docs.abapopenchecks.org/checks/07/`,
      tags: [RuleTag.Styleguide, RuleTag.Quickfix, RuleTag.SingleFile],
      badExample: `CALL METHOD zcl_class=>method( ).
CALL METHOD cl_abap_typedescr=>describe_by_name
  EXPORTING
    p_name         = 'NAME'
  RECEIVING
    p_descr_ref    = lr_typedescr
  EXCEPTIONS
    type_not_found = 1
    OTHERS         = 2.`,
      goodExample: `zcl_class=>method( ).
cl_abap_typedescr=>describe_by_name(
  EXPORTING
    p_name         = 'NAME'
  RECEIVING
    p_descr_ref    = lr_typedescr
  EXCEPTIONS
    type_not_found = 1
    OTHERS         = 2 ).`,
    };
  }
 
  private getMessage(): string {
    return "Use functional writing style for method calls";
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: FunctionalWritingConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile, obj: ABAPObject): readonly Issue[] {
    const issues: Issue[] = [];
    let exception = false;
 
    if (obj.getType() === "INTF") {
      return [];
    }
 
    let definition: InfoClassDefinition | undefined = undefined;
    if (obj instanceof Class) {
      definition = obj.getClassDefinition();
    }
 
    const ddic = new DDIC(this.reg);
 
    for (const statNode of file.getStatements()) {
      if (statNode.get() instanceof Statements.ClassImplementation
        && definition
        && ddic.isException(definition, obj)
        && this.conf.ignoreExceptions) {
        exception = true;
      } else if (statNode.get() instanceof Statements.EndClass) {
        exception = false;
      } else if (exception === false && statNode.get() instanceof Statements.Call) {
        if (statNode.getFirstChild()?.get() instanceof Expressions.MethodCallChain) {
          continue;
        }
 
        const dynamic = statNode.findDirectExpression(Expressions.MethodSource)?.findDirectExpression(Expressions.Dynamic);
        if (dynamic !== undefined) {
          continue;
        }
        issues.push(this.createIssueForStatementNode(file, statNode));
      }
    }
 
    return issues;
  }
 
  private createIssueForStatementNode(file: ABAPFile, statNode: StatementNode): Issue {
    const fixString = this.buildFixString(statNode);
    const fix = EditHelper.replaceRange(file, statNode.getStart(), statNode.getEnd(), fixString);
    return Issue.atStatement(file, statNode, this.getMessage(), this.getMetadata().key, this.conf.severity, fix);
  }
 
  private buildFixString(statNode: StatementNode) {
    // Note: line breaks from source are lost
    const methodSource = statNode.findDirectExpression(Expressions.MethodSource);
    let methodSourceStr = methodSource?.concatTokens();
    const methodBody = statNode.findDirectExpression(Expressions.MethodCallBody);
    let methodBodyStr = "";
    if (methodBody) {
      const methodCallParam = methodBody.findDirectExpression(Expressions.MethodCallParam);
      if (methodCallParam && methodCallParam.getFirstToken().getStr() === "(") {
        // has parameters and parantheses
        methodBodyStr = `${methodBody.concatTokens()}.`;
      } else {
        // has parameters, but parentheses are missing
        methodSourceStr = `${methodSourceStr}( `;
        methodBodyStr = `${methodBody.concatTokens()} ).`;
      }
    }
    else {
      // no body means no parentheses and no parameters
      methodBodyStr = "( ).";
    }
    return methodSourceStr + methodBodyStr;
  }
 
}