All files / src/rules preferred_parameter_ignored.ts

100% Statements 113/113
92.85% Branches 26/28
100% Functions 7/7
100% Lines 113/113

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 1141x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14683x 14683x 14683x 14683x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 128634x 14683x 14683x 14120x 14120x 14683x 14683x 264x 264x 14683x 14683x 303x 303x 303x 1689x 1689x 1656x 1656x 33x 1689x 24x 24x 9x 9x 1689x 4x 4x 5x 5x 1689x 1689x 1689x 1689x 303x 303x 303x 14683x 14683x 14683x 14683x 14683x 14683x 33x 33x 188x 188x 9x 9x 188x 24x 24x 14683x 14683x 14683x 14683x 9x 9x 20x 20x 14x 14x 6x 6x 9x 9x 14683x 14683x  
import * as Expressions from "../abap/2_statements/expressions";
import {Issue} from "../issue";
import {BasicRuleConfig} from "./_basic_rule_config";
import {ABAPRule} from "./_abap_rule";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {EditHelper, IEdit} from "../edit_helper";
import {ExpressionNode} from "../abap/nodes";
 
export class PreferredParameterIgnoredConf extends BasicRuleConfig {
}
 
export class PreferredParameterIgnored extends ABAPRule {
  private conf = new PreferredParameterIgnoredConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "preferred_parameter_ignored",
      title: "PREFERRED PARAMETER is ignored",
      shortDescription: `PREFERRED PARAMETER on an IMPORTING clause that still has a mandatory
parameter. The addition names the parameter a positional call fills, which only
means something when every parameter may be left out - so with a mandatory one
present it does nothing.`,
      extendedInformation: `\`PREFERRED PARAMETER\` names the importing parameter that a call filling exactly
one parameter positionally fills. That question only arises when a parameter
may be LEFT OUT, so the addition is ignored while any importing parameter is
neither \`OPTIONAL\` nor carries a \`DEFAULT\`.
 
The compiler warns "Declare the parameter as OPTIONAL. The addition PREFERRED
PARAMETER is ignored if non-optional parameters are used", yet it still lets a
call leave the preferred parameter out. So the quick fix declares the mandatory
importing parameters OPTIONAL, as the compiler asks: every call that compiles
today keeps compiling. Removing the addition instead would turn the calls that
leave the preferred parameter out into syntax errors.`,
      tags: [RuleTag.SingleFile, RuleTag.Quickfix],
      badExample: `METHODS meth
  IMPORTING
    val   TYPE string
    other TYPE i OPTIONAL
      PREFERRED PARAMETER val.`,
      goodExample: `METHODS meth
  IMPORTING
    val   TYPE string OPTIONAL
    other TYPE i OPTIONAL
      PREFERRED PARAMETER val.`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: PreferredParameterIgnoredConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile): Issue[] {
    const issues: Issue[] = [];
 
    for (const statement of file.getStatements()) {
      const importing = statement.findFirstExpression(Expressions.MethodDefImporting);
      if (importing === undefined) {
        continue;
      }
      const preferred = this.preferredTokens(importing);
      if (preferred === undefined) {
        continue;
      }
 
      const mandatory = this.mandatoryParameters(importing);
      if (mandatory.length === 0) {
        continue;
      }
 
      const fix: IEdit = EditHelper.mergeList(mandatory.map(p => EditHelper.insertAt(file, p.getLastToken().getEnd(), " OPTIONAL")));
      const name = mandatory[0].findFirstExpression(Expressions.MethodParamName)?.concatTokens() ?? mandatory[0].concatTokens();
      const message = `PREFERRED PARAMETER is ignored while ${name} is not optional`;
      issues.push(Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, fix));
    }
 
    return issues;
  }
 
////////////////
 
  /** The span of `PREFERRED PARAMETER <name>`, or undefined when the clause
   *  does not carry the addition. */
  private preferredTokens(importing: ExpressionNode) {
    const tokens = importing.getTokens();
    for (let i = 0; i + 2 < tokens.length; i++) {
      if (tokens[i].getStr().toUpperCase() === "PREFERRED"
          && tokens[i + 1].getStr().toUpperCase() === "PARAMETER") {
        return {start: tokens[i].getStart(), end: tokens[i + 2].getEnd()};
      }
    }
    return undefined;
  }
 
  /** The importing parameters that are neither OPTIONAL nor have a DEFAULT,
   *  empty when every one of them may be left out. */
  private mandatoryParameters(importing: ExpressionNode): ExpressionNode[] {
    const ret: ExpressionNode[] = [];
    for (const param of importing.findDirectExpressions(Expressions.MethodParamOptional)) {
      const concat = param.concatTokens().toUpperCase();
      if (concat.endsWith(" OPTIONAL") || concat.includes(" DEFAULT ")) {
        continue;
      }
      ret.push(param);
    }
    return ret;
  }
 
}