All files / src/rules select_performance.ts

98.45% Statements 127/129
91.18% Branches 31/34
100% Functions 8/8
98.45% Lines 127/129

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 1291x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 15306x 15306x 15306x 15306x 15306x 15306x 15306x 15306x 1x 7654x 7654x 7654x 7654x 7654x 22822x 22822x 22822x 22822x 22822x 22822x 22822x 22822x 22822x 22822x 7654x 7654x 148x 148x 148x 7654x 7654x 7347x     7347x 7347x 7654x 7654x 139x 139x 7654x 7654x 174x 20x 20x 154x 154x 154x 174x 158x 158x 12x 12x 146x 146x 146x 4x 4x 1x 1x 3x 3x 3x 146x 146x 146x 146x 146x 146x 146x 146x 10x 10x 10x 3x 10x 7x 2x 2x 5x 5x 5x 10x 1x 1x 4x 4x 4x 4x 146x 158x 142x 142x 142x 7654x 7654x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 5x 4x 4x 7654x 7654x
import * as Expressions from "../abap/2_statements/expressions";
import * as Statements from "../abap/2_statements/statements";
import * as Structures from "../abap/3_structures/structures";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Issue} from "../issue";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
import {IObject} from "../objects/_iobject";
import {ABAPObject} from "../objects/_abap_object";
import {IRegistry} from "../_iregistry";
import {SyntaxLogic} from "../abap/5_syntax/syntax";
import {Table} from "../objects/table";
import {StructureType} from "../abap/types/basic/structure_type";
import {ISpaghettiScope} from "../abap/5_syntax/_spaghetti_scope";
import {StatementNode} from "../abap/nodes/statement_node";
import {IFile} from "../files/_ifile";
 
const DEFAULT_COLUMNS = 10;
 
export class SelectPerformanceConf extends BasicRuleConfig {
  /** Detects ENDSELECT */
  public endSelect: boolean = true;
  /** Detects SELECT * */
  public selectStar: boolean = true;
  /** "SELECT" * is considered okay if the table is less than X columns, the table must be known to the linter */
  public starOkayIfFewColumns: number = DEFAULT_COLUMNS;
}
 
export class SelectPerformance implements IRule {
  protected reg: IRegistry;
  private conf = new SelectPerformanceConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "select_performance",
      title: "SELECT performance",
      shortDescription: `Various checks regarding SELECT performance.`,
      extendedInformation: `ENDSELECT: not reported when the corresponding SELECT has PACKAGE SIZE
 
SELECT *: not reported if using INTO/APPENDING CORRESPONDING FIELDS OF`,
      tags: [RuleTag.SingleFile, RuleTag.Performance],
    };
  }
 
  public initialize(reg: IRegistry) {
    this.reg = reg;
    return this;
  }
 
  public getConfig() {
    if (this.conf.starOkayIfFewColumns === undefined) {
      this.conf.starOkayIfFewColumns = DEFAULT_COLUMNS;
    }
    return this.conf;
  }
 
  public setConfig(conf: SelectPerformanceConf): void {
    this.conf = conf;
  }
 
  public run(obj: IObject): readonly Issue[] {
    if (!(obj instanceof ABAPObject)) {
      return [];
    }
 
    const issues: Issue[] = [];
 
    for (const file of obj.getABAPFiles()) {
      const stru = file.getStructure();
      if (stru === undefined) {
        return issues;
      }
 
      if (this.conf.endSelect) {
        for (const s of stru.findAllStructures(Structures.Select) || []) {
          const select = s.findDirectStatement(Statements.SelectLoop);
          if (select === undefined || select.concatTokens().includes("PACKAGE SIZE")) {
            continue;
          }
          const message = "Avoid use of ENDSELECT";
          issues.push(Issue.atStatement(file, select, message, this.getMetadata().key, this.conf.severity));
        }
      }
 
      if (this.conf.selectStar) {
        const spaghetti = new SyntaxLogic(this.reg, obj).run().spaghetti;
 
        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 * ") === false
              && concat.startsWith("SELECT SINGLE * ") === false) {
            continue;
          } else if (concat.includes(" INTO CORRESPONDING FIELDS OF ")
              || concat.includes(" APPENDING CORRESPONDING FIELDS OF ")) {
            continue;
          }
 
          const columnCount = this.findNumberOfColumns(s, file, spaghetti);
          if (columnCount
              && columnCount <= this.getConfig().starOkayIfFewColumns) {
            continue;
          }
 
          const message = "Avoid use of SELECT *";
          issues.push(Issue.atToken(file, s.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
        }
      }
    }
 
    return issues;
  }
 
  private findNumberOfColumns(s: StatementNode, file: IFile, spaghetti: ISpaghettiScope): number | undefined {
    const dbnames = s.findAllExpressions(Expressions.DatabaseTable);
    if (dbnames.length === 1) {
      const start = dbnames[0].getFirstToken().getStart();
      const scope = spaghetti.lookupPosition(start, file.getFilename());
      const name = scope?.findTableReference(start);
      const tabl = this.reg.getObject("TABL", name) as Table | undefined;
      const parsed = tabl?.parseType(this.reg);
      if (parsed instanceof StructureType) {
        return parsed.getComponents().length;
      }
    }
    return undefined;
  }
 
}