All files / src/abap/5_syntax _loop_table_change.ts

100% Statements 93/93
93.61% Branches 44/47
100% Functions 5/5
100% Lines 93/93

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 941x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4258x 4258x 4258x 4258x 4258x 184x 140x 140x 44x 184x 1x 1x 43x 43x 184x 184x 184x 1x 1x 42x 184x 2x 2x 40x 40x 40x 4258x 4258x 40x 40x 4258x 4258x 16155x 16018x 16018x 137x 137x 137x 137x 137x 16155x 16155x 16155x 114x 114x 23x 16155x 24x 24x 24x 1x 24x 2x 2x 21x 24x 7x 24x 2x 2x 2x 12x 12x 12x 12x 11x 4258x 4258x 63x 63x 63x 1x 1x 62x 63x 4x 4x 58x 58x 4258x  
import * as Expressions from "../2_statements/expressions";
import * as Statements from "../2_statements/statements";
import {ExpressionNode, StatementNode, StructureNode, TokenNode} from "../nodes";
import {TypedIdentifier} from "../types/_typed_identifier";
import {TableType} from "../types/basic";
import {CurrentScope} from "./_current_scope";
import {ScopeType} from "./_scope_type";
import {SyntaxInput, syntaxIssue} from "./_syntax_input";
 
/** In a method, the body of the table an enclosing LOOP AT walks cannot be replaced or freed,
 *  "you cannot access the entire table body in change mode in a loop. If this is known statically,
 *  a syntax error occurs in classes", see the ABAP keyword documentation for LOOP AT itab.
 *  Only tables named directly are handled, a field symbol or a reference is a runtime matter. */
export class LoopTableChange {
  private readonly tables: TypedIdentifier[] = [];
 
  /** returns true if the LOOP structure was pushed, then pop() must be called after it */
  public push(node: StructureNode, scope: CurrentScope): boolean {
    if (scope.getType() !== ScopeType.Method) {
      return false;
    }
    const loop = node.findDirectStatement(Statements.Loop);
    if (loop === undefined || loop.findDirectExpression(Expressions.LoopGroupBy) !== undefined) {
      return false;
    }
    const chain = loop.findDirectExpression(Expressions.LoopSource)
      ?.findDirectExpression(Expressions.SimpleSource2)
      ?.findDirectExpression(Expressions.FieldChain);
    const children = chain?.getChildren() || [];
    if (children.length !== 1 && !(children.length === 2 && children[1].get() instanceof Expressions.TableBody)) {
      return false;
    }
    const found = LoopTableChange.findTable(children[0], scope);
    if (found === undefined) {
      return false;
    }
    this.tables.push(found);
    return true;
  }
 
  public pop(): void {
    this.tables.pop();
  }
 
  public check(node: StatementNode, input: SyntaxInput): void {
    if (this.tables.length === 0 || input.scope.getType() !== ScopeType.Method) {
      return;
    }
 
    // DELETE itab WHERE is not refused, abapGit does it in zcl_abapgit_syntax_xml->order_matches,
    // SORT is left out, it is not certain that it is checked statically
    const s = node.get();
    if (!(s instanceof Statements.Clear)
        && !(s instanceof Statements.Free)
        && !(s instanceof Statements.Refresh)
        && !(s instanceof Statements.Move)) {
      return;
    }
 
    for (const target of node.findDirectExpressions(Expressions.Target)) {
      const children = target.getChildren();
      const body = children.length === 2 && children[1].get() instanceof Expressions.TableBody;
      if (children.length !== 1 && body === false) {
        continue;
      } else if (!(children[0].get() instanceof Expressions.TargetField)) {
        continue;
      }
      const found = LoopTableChange.findTable(children[0], input.scope);
      if (found === undefined || this.tables.includes(found) === false) {
        continue;
      } else if ((found.getType() as TableType).isWithHeader() === true && body === false) {
        // without [] the statement works on the header line
        continue;
      }
      const message = `"${found.getName()}" cannot be changed as a whole inside a LOOP over it`;
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
      return;
    }
  }
 
  private static findTable(node: ExpressionNode | TokenNode | undefined, scope: CurrentScope): TypedIdentifier | undefined {
    if (!(node instanceof ExpressionNode)
        || node.getChildren().length !== 1
        || !(node.getFirstChild()?.get() instanceof Expressions.Field)) {
      return undefined;
    }
    const found = scope.findVariable(node.concatTokens());
    if (found === undefined || !(found.getType() instanceof TableType)) {
      return undefined;
    }
    return found;
  }
}