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 138 139 140 141 142 143 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14718x 14718x 14718x 14718x 14718x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 43945x 14718x 14718x 14120x 14120x 14718x 14718x 264x 264x 14718x 14718x 362x 362x 362x 14718x 14718x 459x 459x 459x 459x 2x 2x 457x 459x 115x 115x 342x 342x 459x 1093x 1x 1x 1093x 139x 139x 139x 139x 139x 95x 95x 44x 44x 139x 139x 139x 139x 139x 139x 139x 139x 139x 139x 139x 139x 139x 139x 139x 20x 20x 24x 139x 24x 24x 139x 139x 139x 16x 16x 24x 24x 24x 24x 1092x 342x 342x 14718x 14718x 14718x 46x 17x 17x 29x 46x 46x 29x 46x 46x 14718x | import * as Expressions from "../abap/2_statements/expressions";
import * as Statements from "../abap/2_statements/statements";
import {StatementNode} from "../abap/nodes";
import {CharacterType} from "../abap/types/basic";
import {EditHelper, IEdit} from "../edit_helper";
import {Issue} from "../issue";
import {IObject} from "../objects/_iobject";
import {IRegistry} from "../_iregistry";
import {LanguageVersion, Release, releaseAtLeast} from "../version";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
import {ObjectAnalysis, OccurrenceKind, Procedure, characterConstant, deleteStatement, ifBranches, initialValue,
mergeEdits, nodeText, plainData, plainMove, sameCharacters, singleTarget} from "./_procedure_analysis";
export class BooleanFlagViaIfConf extends BasicRuleConfig {
}
export class BooleanFlagViaIf implements IRule {
private reg: IRegistry;
private conf = new BooleanFlagViaIfConf();
public getMetadata(): IRuleMetadata {
return {
key: "boolean_flag_via_if",
title: "Boolean flag via IF",
shortDescription: `Finds a flag that is initialized to false and set to true in a single IF, use xsdbool`,
extendedInformation: `Reports DATA(f) = VALUE abap_bool( )., DATA(f) = abap_false. or DATA f TYPE abap_bool.,
later followed by IF cond. f = abap_true. ENDIF., when all of the following hold:
* f is a local variable of a METHOD, FORM or FUNCTION, of type c LENGTH 1, its address is never taken
* the IF has no ELSEIF and no ELSE, its only statement is f = abap_true. and it contains no comments
* f has no other write, and is not read before the IF
* the IF is in the same block and the same loop as the declaration; DATA f TYPE abap_bool. is not
executed per loop iteration, so neither it nor the IF may be in a loop
Requires xsdbool, ie. 7.40 SP08 or above, ABAP Cloud or open-abap.
Objects with syntax errors, macros or unknown statements are skipped, as are procedures with dynamic code,
INCLUDE, test seams, enhancements or native SQL.
The quick fix deletes an inline declaration and replaces the IF with DATA(f) = xsdbool( cond ).,
or keeps DATA f TYPE abap_bool. and replaces the IF with f = xsdbool( cond ).
use_bool_expression covers IF with an ELSE branch, without looking at the declaration and the other uses.`,
tags: [RuleTag.Quickfix],
badExample: `DATA(lv_found) = abap_false.
IF line_exists( lt_rows[ id = lv_id ] ).
lv_found = abap_true.
ENDIF.`,
goodExample: `DATA(lv_found) = xsdbool( line_exists( lt_rows[ id = lv_id ] ) ).`,
};
}
public getConfig() {
return this.conf;
}
public setConfig(conf: BooleanFlagViaIfConf) {
this.conf = conf;
}
public initialize(reg: IRegistry) {
this.reg = reg;
return this;
}
public run(obj: IObject): readonly Issue[] {
const config = this.reg.getConfig();
if (!releaseAtLeast(config.getRelease(), Release.v740sp08)
&& config.getLanguageVersion() !== LanguageVersion.Cloud
&& !config.isOpenABAP()) {
return [];
}
const analysis = ObjectAnalysis.run(this.reg, obj);
if (analysis === undefined) {
return [];
}
const issues: Issue[] = [];
for (const procedure of analysis.procedures) {
if (procedure.supported === false) {
continue;
}
for (const variable of procedure.locals()) {
const declaration = variable.declaration!;
const type = variable.identifier.getType();
const writes = variable.writes();
if (!(type instanceof CharacterType) || type.getLength() !== 1 || variable.escapes()
|| writes.length !== 1 || !this.initiallyFalse(procedure, variable.inline, declaration)) {
continue;
}
const write = writes[0];
const move = plainMove(write.statement);
const value = move === undefined ? undefined : characterConstant(procedure, move.source);
const check = procedure.statements[write.index - 1];
const structure = procedure.ifStructure(check);
const branches = structure === undefined ? undefined : ifBranches(structure);
const endIf = structure?.findDirectStatement(Statements.EndIf);
if (move === undefined || move.operator !== "=" || value === undefined || !sameCharacters(value, "X")
|| singleTarget(move.target)?.getStart().equals(write.token.getStart()) !== true
|| !(check?.get() instanceof Statements.If) || check.getColon() !== undefined
|| structure === undefined || endIf === undefined || branches?.length !== 1
|| branches[0].body?.findAllStatementNodes().length !== 1
|| procedure.block(check) !== procedure.block(declaration)
|| !procedure.sameLoop(check, declaration)
|| (!variable.inline && (procedure.loop(check) !== undefined || procedure.loop(declaration) !== undefined))
|| variable.occurrences.some(o => o.kind === OccurrenceKind.Read && o.index <= procedure.index(endIf))
|| procedure.hasComment(check.getStart(), endIf.getEnd())) {
continue;
}
const cond = check.findDirectExpression(Expressions.Cond);
if (cond === undefined) {
continue;
}
const name = write.token.getStr();
const assignment = (variable.inline ? "DATA(" + name + ")" : name) + " = xsdbool( " + nodeText(procedure.file, cond) + " ).";
const edits: IEdit[] = [EditHelper.replaceRange(procedure.file, check.getStart(), endIf.getEnd(), assignment)];
if (variable.inline) {
edits.push(deleteStatement(procedure.file, declaration));
}
const fix = mergeEdits(procedure.file, edits);
issues.push(Issue.atStatement(procedure.file, check, "Use xsdbool to set " + name,
this.getMetadata().key, this.conf.severity, fix));
}
}
return issues;
}
/** DATA(f) = VALUE abap_bool( ). DATA(f) = abap_false. or DATA f TYPE abap_bool. */
private initiallyFalse(procedure: Procedure, inline: boolean, declaration: StatementNode): boolean {
if (inline === false) {
return plainData(declaration) && declaration.getColon() === undefined;
}
const move = plainMove(declaration);
if (move === undefined || move.operator !== "=" || move.target.getChildren().length !== 1
|| move.target.findDirectExpression(Expressions.InlineData) === undefined) {
return false;
}
const constant = characterConstant(procedure, move.source);
return (constant !== undefined && sameCharacters(constant, "")) || initialValue(move.source);
}
}
|