All files / src/rules select_add_order_by.ts

96.74% Statements 119/123
82.97% Branches 39/47
100% Functions 8/8
96.74% Lines 119/123

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 1231x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 20683x 20683x 20683x 1x 10342x 10342x 10342x 10342x 10342x 30857x 30857x 30857x 30857x 30857x 30857x 30857x 30857x 30857x 30857x 30857x 30857x 30857x 10342x 10342x 9839x 9839x 10342x 10342x 252x 252x 252x 10342x 10342x 241x 241x 10342x 10342x 318x 318x 82x 82x 236x 236x 236x 318x 243x 243x 12x 12x 231x 231x 231x 243x 13x 13x 3x 13x     10x 10x 10x 13x 3x 13x 1x 1x 6x 13x 3x 3x 1x 1x 1x 2x 2x 2x 231x 224x 224x 224x 10342x 10342x 6x 6x 5x 5x 5x 5x 5x 5x 1x 1x     1x 1x 5x 5x 5x 5x 3x 3x 5x 3x 3x 10342x 10342x
import * as Expressions from "../abap/2_statements/expressions";
import * as Statements from "../abap/2_statements/statements";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Issue} from "../issue";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
import {SyntaxLogic} from "../abap/5_syntax/syntax";
import {IObject} from "../objects/_iobject";
import {ABAPObject} from "../objects/_abap_object";
import {IRegistry} from "../_iregistry";
import {StructureType, TableAccessType, TableType} from "../abap/types/basic";
import {StatementNode} from "../abap/nodes";
import {ABAPFile} from "../abap/abap_file";
import {ISpaghettiScope} from "../abap/5_syntax/_spaghetti_scope";
 
 
export class SelectAddOrderByConf extends BasicRuleConfig {
  public skipForAllEntries: boolean = false;
}
 
export class SelectAddOrderBy implements IRule {
  private reg: IRegistry;
  private conf = new SelectAddOrderByConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "select_add_order_by",
      title: "SELECT add ORDER BY",
      shortDescription: `SELECTs add ORDER BY clause`,
      extendedInformation: `
This will make sure that the SELECT statement returns results in the same sequence on different databases
 
add ORDER BY PRIMARY KEY if in doubt
 
If the target is a sorted/hashed table, no issue is reported`,
      tags: [RuleTag.SingleFile],
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public initialize(reg: IRegistry) {
    this.reg = reg;
    return this;
  }
 
  public setConfig(conf: SelectAddOrderByConf): void {
    this.conf = conf;
  }
 
  public run(obj: IObject): Issue[] {
    const issues: Issue[] = [];
    if (!(obj instanceof ABAPObject) || obj.getType() === "INTF") {
      return [];
    }
 
    const spaghetti = new SyntaxLogic(this.reg, obj).run().spaghetti;
 
    for (const file of obj.getABAPFiles()) {
      const stru = file.getStructure();
      if (stru === undefined) {
        return issues;
      }
 
      const selects = stru.findAllStatements(Statements.Select);
      selects.push(...stru.findAllStatements(Statements.SelectLoop));
      for (const s of selects) {
        const concat = s.concatTokens().toUpperCase();
        if (concat.startsWith("SELECT SINGLE ")) {
          continue;
        } else if (this.getConfig()?.skipForAllEntries === true && concat.includes(" FOR ALL ENTRIES ")) {
          continue;
        }
 
        // skip COUNT(*)
        const list = s.findAllExpressions(Expressions.SQLField);
        if (list.length === 1 && list[0].getFirstChild()?.get() instanceof Expressions.SQLAggregation) {
          continue;
        } else if (s.findFirstExpression(Expressions.SQLOrderBy)) {
          continue;
        }
 
        if (this.isTargetSortedOrHashed(s, spaghetti, file)) {
          continue;
        } else if (s.findFirstExpression(Expressions.SQLJoin) && s.findFirstExpression(Expressions.SQLForAllEntries)) {
// see https://github.com/abaplint/abaplint/issues/2957
          continue;
        }
 
        issues.push(Issue.atStatement(file, s, "Add ORDER BY", this.getMetadata().key, this.conf.severity));
      }
    }
 
    return issues;
  }
 
  private isTargetSortedOrHashed(s: StatementNode, spaghetti: ISpaghettiScope, file: ABAPFile): boolean {
    const target = s.findFirstExpression(Expressions.SQLIntoTable)?.findFirstExpression(Expressions.Target);
    if (target) {
      const start = target.getFirstToken().getStart();
      const scope = spaghetti.lookupPosition(start, file.getFilename());
      let type = scope?.findWriteReference(start)?.getType();
 
      const children = target.getChildren();
      if (type instanceof StructureType && children.length >= 3 && children[1].concatTokens() === "-") {
        const found = type.getComponentByName(children[2].concatTokens());
        if (found === undefined) {
          return false;
        }
        type = found;
      }
 
      if (type instanceof TableType
          && (type?.getAccessType() === TableAccessType.sorted
          || type?.getAccessType() === TableAccessType.hashed)) {
        return true;
      }
    }
    return false;
  }
 
}