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 136 137 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14682x 14682x 14682x 14682x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 43915x 14682x 14682x 14120x 14120x 14682x 14682x 264x 264x 14682x 14682x 312x 312x 312x 312x 1713x 1713x 1700x 1700x 13x 13x 13x 1713x 13x 13x 13x 1713x 6x 6x 7x 7x 7x 7x 7x 312x 312x 312x 14682x 14682x 14682x 14682x 19x 19x 61x 61x 15x 15x 61x 4x 4x 14682x 14682x 13x 13x 13x 1x 1x 13x 8x 8x 4x 13x 13x 13x 1x 1x 14682x 14682x 10x 10x 10x 15x 15x 15x 2x 15x 11x 9x 9x 9x 9x 11x 2x 2x 13x 2x 2x 2x 15x 10x 10x 14682x 14682x | import * as Statements from "../abap/2_statements/statements";
import * as Expressions from "../abap/2_statements/expressions";
import {Issue} from "../issue";
import {BasicRuleConfig} from "./_basic_rule_config";
import {ABAPRule} from "./_abap_rule";
import {IRuleMetadata, RuleTag} from "./_irule";
import {StatementNode} from "../abap/nodes/statement_node";
import {TokenNode} from "../abap/nodes/token_node";
import {Comment} from "../abap/2_statements/statements/_statement";
import {ABAPFile} from "../abap/abap_file";
export class DeleteIndexInLoopConf extends BasicRuleConfig {
}
export class DeleteIndexInLoop extends ABAPRule {
private conf = new DeleteIndexInLoopConf();
public getMetadata(): IRuleMetadata {
return {
key: "delete_index_in_loop",
title: "DELETE INDEX sy-tabix inside a LOOP over the same table",
shortDescription: `Reports DELETE itab INDEX sy-tabix inside a LOOP AT the same table`,
extendedInformation: `sy-tabix is the index of the loop's current row only until another statement writes it.
READ TABLE, APPEND, INSERT, COLLECT, a nested LOOP and any method called in the loop body
all set sy-tabix. After that the DELETE removes a different row, and if sy-tabix is 0 it
raises the runtime error TABLE_INVALID_INDEX.
Use DELETE itab WHERE ..., or collect the rows to keep in a second table.
Not reported: DELETE itab INDEX sy-tabix directly after READ TABLE itab, where sy-tabix
is the index the READ just set.`,
tags: [RuleTag.SingleFile],
badExample: `LOOP AT tab INTO row.
IF row-flag = abap_true.
DELETE tab INDEX sy-tabix.
ENDIF.
ENDLOOP.`,
goodExample: `DELETE tab WHERE flag = abap_true.`,
};
}
public getConfig() {
return this.conf;
}
public setConfig(conf: DeleteIndexInLoopConf) {
this.conf = conf;
}
public runParsed(file: ABAPFile): Issue[] {
const issues: Issue[] = [];
const statements = file.getStatements();
for (let i = 0; i < statements.length; i++) {
const statement = statements[i];
if (!(statement.get() instanceof Statements.DeleteInternal) || this.byTabix(statement) === false) {
continue;
}
// DELETE TABLE and DELETE ADJACENT DUPLICATES have a keyword before the target
const target = statement.findDirectExpression(Expressions.Target);
if (target === undefined || statement.getChildren()[1] !== target) {
continue;
}
const table = target.concatTokens().toUpperCase();
if (this.previousReadsTable(i, statements, table) === true
|| this.enclosingLoopTables(i, statements).includes(table) === false) {
continue;
}
const message = `DELETE ${target.concatTokens()} INDEX sy-tabix inside LOOP AT the same table,`
+ ` sy-tabix might not be the loop's current row`;
issues.push(Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
}
return issues;
}
////////////////
private byTabix(statement: StatementNode): boolean {
const children = statement.getChildren();
for (let i = 0; i < children.length - 1; i++) {
const child = children[i];
if (child instanceof TokenNode && child.getFirstToken().getStr().toUpperCase() === "INDEX") {
return children[i + 1].concatTokens().toUpperCase() === "SY-TABIX";
}
}
return false;
}
private previousReadsTable(index: number, statements: readonly StatementNode[], table: string): boolean {
for (let i = index - 1; i >= 0; i--) {
const statement = statements[i];
if (statement.get() instanceof Comment) {
continue;
}
if (!(statement.get() instanceof Statements.ReadTable)) {
return false;
}
const read = statement.findDirectExpression(Expressions.SimpleSource2)
?? statement.findDirectExpression(Expressions.Source);
return read?.concatTokens().toUpperCase() === table;
}
return false;
}
private enclosingLoopTables(index: number, statements: readonly StatementNode[]): string[] {
const tables: string[] = [];
let depth = 0;
for (let i = index - 1; i >= 0; i--) {
const statement = statements[i];
const s = statement.get();
if (s instanceof Statements.EndLoop) {
depth++;
} else if (s instanceof Statements.Loop) {
if (depth === 0) {
const source = statement.findDirectExpression(Expressions.LoopSource);
if (source !== undefined) {
tables.push(source.concatTokens().toUpperCase());
}
} else {
depth--;
}
} else if (s instanceof Statements.MethodImplementation
|| s instanceof Statements.Form
|| s instanceof Statements.FunctionModule
|| s instanceof Statements.Module) {
break;
}
}
return tables;
}
}
|