All files / src/rules copy_of_initial_variable.ts

100% Statements 108/108
100% Branches 37/37
100% Functions 6/6
100% Lines 108/108

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 1091x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14721x 14721x 14721x 14721x 14721x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 100440x 14721x 14721x 14120x 14120x 14721x 14721x 264x 264x 14721x 14721x 385x 385x 385x 14721x 14721x 482x 482x 115x 115x 367x 367x 482x 1368x 1x 1x 1368x 141x 141x 141x 141x 141x 141x 141x 87x 87x 54x 54x 141x 141x 141x 15x 15x 39x 39x 39x 141x 5x 5x 34x 34x 34x 34x 34x 141x 11x 11x 34x 34x 34x 34x 1367x 367x 367x 14721x  
import * as Expressions from "../abap/2_statements/expressions";
import {EditHelper, IEdit} from "../edit_helper";
import {Issue} from "../issue";
import {IObject} from "../objects/_iobject";
import {IRegistry} from "../_iregistry";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
import {ObjectAnalysis, deleteStatement, identicalTypes, initialDeclaration, isReferenceType, mergeEdits, nodeText,
  plainClear, plainMove, singleField, targetType} from "./_procedure_analysis";
 
export class CopyOfInitialVariableConf extends BasicRuleConfig {
}
 
export class CopyOfInitialVariable implements IRule {
  private reg: IRegistry;
  private conf = new CopyOfInitialVariableConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "copy_of_initial_variable",
      title: "Copy of initial variable",
      shortDescription: `Finds assignments from a local variable that is always initial`,
      extendedInformation: `Reports DATA v TYPE ... [CLEAR v.] x = v. when all of the following hold:
 
* v is a local variable of a METHOD, FORM or FUNCTION, declared with DATA without VALUE, or with
  DATA(v) = VALUE t( ). with an explicit type and nothing in the parentheses. Both make v initial, the latter
  every time it is executed, so v is initial wherever it is read, also in loops
* v is never changed, except by CLEAR v., and its address is never taken
* v has exactly one read, the complete source of x = v., where x is not an inline declaration
* x and v are both reference types, or have identical types. Initial values convert differently between
  types, e.g. an initial d becomes '00000000' in a string
* the declaration and the CLEARs are not chained
 
Objects with syntax errors, macros or unknown statements are skipped, as are procedures with dynamic code,
INCLUDE, test seams, enhancements or native SQL.
 
The quick fix deletes the declaration and the CLEARs, and replaces x = v. with CLEAR x.
No release requirement.`,
      tags: [RuleTag.Quickfix],
      badExample: `DATA lo_empty TYPE REF TO zcl_node.
ro_result = lo_empty.`,
      goodExample: `CLEAR ro_result.`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: CopyOfInitialVariableConf) {
    this.conf = conf;
  }
 
  public initialize(reg: IRegistry) {
    this.reg = reg;
    return this;
  }
 
  public run(obj: IObject): readonly Issue[] {
    const analysis = ObjectAnalysis.run(this.reg, obj);
    if (analysis === undefined) {
      return [];
    }
 
    const issues: Issue[] = [];
    for (const procedure of analysis.procedures) {
      if (procedure.supported === false) {
        continue;
      }
      for (const variable of procedure.locals()) {
        const declaration = variable.declaration!;
        const reads = variable.reads();
        const writes = variable.writes();
        if (initialDeclaration(variable) === undefined || declaration.getColon() !== undefined
            || variable.escapes() || reads.length !== 1
            || writes.some(w => plainClear(w.statement)?.getStart().equals(w.token.getStart()) !== true
              || w.statement.getColon() !== undefined)) {
          continue;
        }
        const read = reads[0];
        const move = plainMove(read.statement);
        if (move === undefined || move.operator !== "="
            || singleField(move.source)?.getStart().equals(read.token.getStart()) !== true
            || move.target.findFirstExpression(Expressions.InlineData) !== undefined) {
          continue;
        }
        const type = variable.identifier.getType();
        const target = targetType(procedure, move.target);
        if (target === undefined
            || (!(isReferenceType(type) && isReferenceType(target)) && !identicalTypes(type, target))) {
          continue;
        }
 
        const edits: IEdit[] = [
          deleteStatement(procedure.file, declaration),
          EditHelper.replaceRange(procedure.file, read.statement.getStart(), read.statement.getEnd(),
                                  "CLEAR " + nodeText(procedure.file, move.target) + ".")];
        for (const write of writes) {
          edits.push(deleteStatement(procedure.file, write.statement));
        }
        const fix = mergeEdits(procedure.file, edits);
        const message = variable.identifier.getName() + " is always initial, use CLEAR";
        issues.push(Issue.atStatement(procedure.file, read.statement, message, this.getMetadata().key, this.conf.severity, fix));
      }
    }
    return issues;
  }
}