All files / src/rules select_performance.ts

98.52% Statements 134/136
91.17% Branches 31/34
100% Functions 8/8
98.52% Lines 134/136

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 1361x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 21784x 21784x 21784x 21784x 21784x 21784x 21784x 21784x 21784x 21784x 1x 10893x 10893x 10893x 10893x 10893x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 32497x 10893x 10893x 267x 267x 267x 10893x 10893x 10347x     10347x 10347x 10893x 10893x 258x 258x 10893x 10893x 333x 63x 63x 270x 270x 270x 333x 277x 277x 12x 12x 265x 265x 265x 5x 5x 1x 1x 4x 4x 4x 265x 265x 265x 265x 265x 265x 265x 265x 12x 12x 12x 4x 12x 8x 2x 2x 6x 6x 6x 12x 2x 2x 4x 4x 4x 4x 265x 277x 258x 258x 258x 10893x 10893x 6x 6x 6x 6x 6x 6x 6x 6x 2x 2x 6x 4x 4x 10893x 10893x
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
   * @default 10
   */
  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;
  }
 
}