All files / src/rules select_performance.ts

98.41% Statements 62/63
85% Branches 34/40
100% Functions 8/8
98.41% Lines 62/63

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   14530x   14530x   14530x     1x   7266x     21667x                       123x 123x       7009x     7009x       114x       141x 8x     133x   133x 137x 137x 12x     125x 125x 4x 4x 1x   3x 3x       125x 125x   125x 125x 125x 10x 10x   3x 7x   2x     5x 5x   1x     4x 4x         121x       5x 5x 5x 5x 5x 5x 5x 5x 1x     4x      
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() {
    Iif (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;
      }
 
      Eif (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));
        }
      }
 
      Eif (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);
    Eif (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;
  }
 
}