All files / src/rules redundant_cast.ts

89.33% Statements 134/150
71.42% Branches 30/42
100% Functions 8/8
89.33% Lines 134/150

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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 1511x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14683x 14683x 14683x 14683x 14683x 114515x 114515x 114515x 114515x 114515x 114515x 114515x 114515x 114515x 114515x 114515x 114515x 14683x 14683x 14120x 14120x 14683x 14683x 264x 264x 14683x 14683x 274x 274x 274x 14683x 14683x 371x 371x     371x 371x 80x 80x 291x 291x 371x 8x 8x 283x 283x 371x 293x 293x 12x 12x 281x 293x 10x 10x 10x 10x 10x     10x 10x 10x     10x 10x 50x 10x 10x     10x 10x 10x 4x 4x 6x 6x 6x 10x     6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 281x 283x 283x 283x 14683x 14683x 14683x 10x 10x     10x 10x 10x 10x 10x     10x 10x 26x 10x 10x     10x 10x 14683x 14683x 10x 3x 3x 7x 7x 14683x  
import * as Expressions from "../abap/2_statements/expressions";
import {ReferenceType} from "../abap/5_syntax/_reference";
import {ISpaghettiScopeNode} from "../abap/5_syntax/_spaghetti_scope";
import {SyntaxLogic} from "../abap/5_syntax/syntax";
import {ExpressionNode} from "../abap/nodes";
import {TypedIdentifier} from "../abap/types/_typed_identifier";
import {AbstractType} from "../abap/types/basic/_abstract_type";
import {ObjectReferenceType} from "../abap/types/basic";
import {Issue} from "../issue";
import {ABAPObject} from "../objects/_abap_object";
import {IObject} from "../objects/_iobject";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
import {IRegistry} from "../_iregistry";
import {EditHelper} from "../edit_helper";
import {Release, releaseAtLeast} from "../version";
 
export class RedundantCastConf extends BasicRuleConfig {
}
 
export class RedundantCast implements IRule {
  private reg: IRegistry;
  private conf = new RedundantCastConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "redundant_cast",
      title: "Redundant Cast",
      shortDescription: `Find redundant CAST expressions`,
      extendedInformation: `Reports CAST expressions whose operand is already a reference to the cast's target class or interface.`,
      tags: [RuleTag.Quickfix],
      badExample: `DATA ref TYPE REF TO lcl.
DATA(copy) = CAST lcl( ref ).`,
      goodExample: `DATA ref TYPE REF TO lcl.
DATA(copy) = ref.`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: RedundantCastConf) {
    this.conf = conf;
  }
 
  public initialize(reg: IRegistry) {
    this.reg = reg;
    return this;
  }
 
  public run(obj: IObject): readonly Issue[] {
    if (!releaseAtLeast(this.reg.getConfig().getRelease(), Release.v740sp02)
        && !this.reg.getConfig().isOpenABAP()) {
      return [];
    }
 
    if (!(obj instanceof ABAPObject)) {
      return [];
    }
 
    const syntax = new SyntaxLogic(this.reg, obj).run();
    if (syntax.issues.length > 0) {
      return [];
    }
 
    const issues: Issue[] = [];
    for (const file of obj.getABAPFiles()) {
      const structure = file.getStructure();
      if (structure === undefined) {
        continue;
      }
 
      for (const cast of structure.findAllExpressionsRecursive(Expressions.Cast)) {
        const typeExpression = cast.findDirectExpression(Expressions.TypeNameOrInfer);
        const source = cast.findDirectExpression(Expressions.Source);
        if (typeExpression === undefined
            || source === undefined
            || cast.findDirectExpression(Expressions.Let) !== undefined) {
          continue;
        }
 
        const scope = syntax.spaghetti.lookupPosition(cast.getFirstToken().getStart(), file.getFilename());
        if (scope === undefined) {
          continue;
        }
 
        const target = scope.getData().references.find(reference =>
          reference.referenceType === ReferenceType.InferredType
          && reference.position.getStart().equals(typeExpression.getFirstToken().getStart()))?.resolved;
        if (!(target instanceof TypedIdentifier)) {
          continue;
        }
 
        const operandType = this.sourceType(source, scope);
        if (operandType === undefined || this.sameType(target.getType(), operandType) === false) {
          continue;
        }
 
        const closingParen = cast.getDirectTokens().find(token =>
          token.getStr() === ")" && token.getStart().isAfter(source.getLastToken().getStart()));
        if (closingParen === undefined) {
          continue;
        }
 
        const fix = EditHelper.replaceRange(file, cast.getFirstToken().getStart(), closingParen.getEnd(), source.concatTokens());
        issues.push(Issue.atRange(
          file,
          cast.getFirstToken().getStart(),
          closingParen.getEnd(),
          "Redundant CAST expression",
          this.getMetadata().key,
          this.conf.severity,
          fix));
      }
    }
 
    return issues;
  }
 
  /** The type of a plain variable or attribute, undefined for anything more complex */
  private sourceType(source: ExpressionNode, scope: ISpaghettiScopeNode): AbstractType | undefined {
    const children = source.getChildren();
    if (children.length !== 1 || !(children[0].get() instanceof Expressions.FieldChain)) {
      return undefined;
    }
 
    const fieldChain = children[0] as ExpressionNode;
    const last = fieldChain.getLastChild();
    if (!(last instanceof ExpressionNode)
        || !(last.get() instanceof Expressions.SourceField || last.get() instanceof Expressions.AttributeName)) {
      return undefined;
    }
 
    const operand = scope.getData().references.find(reference =>
      reference.referenceType === ReferenceType.DataReadReference
      && reference.position.getStart().equals(last.getFirstToken().getStart()))?.resolved;
    if (!(operand instanceof TypedIdentifier)) {
      return undefined;
    }
    return operand.getType();
  }
 
  private sameType(first: AbstractType, second: AbstractType): boolean {
    if (!(first instanceof ObjectReferenceType) || !(second instanceof ObjectReferenceType)) {
      return false;
    }
    return first.getIdentifierName().toUpperCase() === second.getIdentifierName().toUpperCase();
  }
}