All files / src/rules select_performance.ts

98.5% Statements 132/134
91.17% Branches 31/34
100% Functions 8/8
98.5% Lines 132/134

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 1341x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 18722x 18722x 18722x 18722x 18722x 18722x 18722x 18722x 1x 9362x 9362x 9362x 9362x 9362x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 27932x 9362x 9362x 225x 225x 225x 9362x 9362x 8900x     8900x 8900x 9362x 9362x 216x 216x 9362x 9362x 284x 51x 51x 233x 233x 233x 284x 240x 240x 12x 12x 228x 228x 228x 5x 5x 1x 1x 4x 4x 4x 228x 228x 228x 228x 228x 228x 228x 228x 12x 12x 12x 4x 12x 8x 2x 2x 6x 6x 6x 12x 2x 2x 4x 4x 4x 4x 228x 240x 221x 221x 221x 9362x 9362x 6x 6x 6x 6x 6x 6x 6x 6x 2x 2x 6x 4x 4x 9362x 9362x
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],
      badExample: `SELECT field1, field2 FROM table
    INTO @DATA(structure) UP TO 1 ROWS ORDER BY field3 DESCENDING.
ENDSELECT.`,
      goodExample: `SELECT field1, field2 FROM table UP TO 1 ROWS
  INTO TABLE @DATA(table) ORDER BY field3 DESCENDING`,
    };
  }
 
  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().toUpperCase().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;
  }
 
}