All files / src/rules alias_never_reassigned.ts

100% Statements 103/103
100% Branches 39/39
100% Functions 6/6
100% Lines 103/103

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 1041x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14716x 14716x 14716x 14716x 14716x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 43944x 14716x 14716x 14120x 14120x 14716x 14716x 264x 264x 14716x 14716x 356x 356x 356x 14716x 14716x 453x 453x 115x 115x 338x 338x 453x 1049x 1x 1x 1049x 170x 170x 170x 170x 170x 170x 170x 170x 134x 134x 36x 170x 170x 170x 31x 170x 13x 13x 23x 170x 51x 51x 23x 23x 23x 23x 1048x 338x 338x 14716x  
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, OccurrenceKind, SourceKind, dataSource, deleteStatement, isCondition, mergeEdits,
  plainMove} from "./_procedure_analysis";
 
export class AliasNeverReassignedConf extends BasicRuleConfig {
}
 
export class AliasNeverReassigned implements IRule {
  private reg: IRegistry;
  private conf = new AliasNeverReassignedConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "alias_never_reassigned",
      title: "Alias never reassigned",
      shortDescription: `Finds DATA(a) = b. where neither a nor b changes afterwards, so b can be read instead of a`,
      extendedInformation: `Reports DATA(a) = b. when all of the following hold:
 
* a is a local variable of a METHOD, FORM or FUNCTION, and has at least two more references, all of them reads,
  which do not take its address, bind a field symbol to it, or pass it to USING, CHANGING or EXPORTING
* b is a local variable, or a component of one, whose address is never taken and which is not written
  after the declaration of a; or b is a constant or me
* the declaration is executed before every read on every path: each read is in the block of the declaration
  or a nested block
 
a has the type of b, so reading b instead does not change any conversion.
A constant b read in IF, ELSEIF, WHILE, CHECK, CASE or WHEN is not reported, see constant_condition.
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 replaces every read of a with b. No release requirement.
 
See also unnecessary_copy for copies that are read once, also of attributes and parameters.`,
      tags: [RuleTag.Quickfix],
      badExample: `DATA(lo_file) = lo_input.
lo_file->read( ).
rv_name = lo_file->get_name( ).`,
      goodExample: `lo_input->read( ).
rv_name = lo_input->get_name( ).`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: AliasNeverReassignedConf) {
    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 move = variable.inline ? plainMove(declaration) : undefined;
        const reads = variable.occurrences;
        if (move === undefined || move.operator !== "="
            || move.target.getChildren().length !== 1
            || move.target.findDirectExpression(Expressions.InlineData) === undefined
            || reads.length < 2 || reads.some(r => r.kind !== OccurrenceKind.Read)
            || reads.some(r => !procedure.dominates(declaration, r.statement))) {
          continue;
        }
        const source = dataSource(procedure, move.source);
        if (source === undefined || source.base === variable
            || (source.kind === SourceKind.Immutable && reads.some(r => isCondition(r.statement)))
            || (source.kind === SourceKind.Local
            && (source.base!.escapes() || source.base!.writes().some(w => w.index >= procedure.index(declaration))))
            || source.kind === SourceKind.Volatile) {
          continue;
        }
        const edits: IEdit[] = [deleteStatement(procedure.file, declaration)];
        for (const read of reads) {
          edits.push(EditHelper.replaceToken(procedure.file, read.token, source.text));
        }
        const fix = mergeEdits(procedure.file, edits);
        const message = variable.identifier.getName() + " is a copy of " + source.text + ", and neither changes";
        issues.push(Issue.atStatement(procedure.file, declaration, message, this.getMetadata().key, this.conf.severity, fix));
      }
    }
    return issues;
  }
}