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 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 29365x 29365x 29365x 29365x 1x 14683x 14683x 14683x 14683x 14683x 44205x 44205x 44205x 44205x 44205x 44205x 44205x 44205x 44205x 44205x 44205x 44205x 14683x 14683x 14120x 14120x 14683x 14683x 265x 265x 14683x 14683x 309x 309x 12x 12x 297x 297x 297x 297x 297x 309x 21x 21x 21x 21x 6x 6x 15x 21x 14x 14x 7x 7x 14x 8x 8x 8x 297x 297x 297x 14683x 14683x 14x 14x 14x 3x 3x 11x 11x 14x 19x 19x 2x 19x 17x 17x 12x 17x 5x 5x 5x 5x 5x 19x 1x 1x 19x 10x 10x 10x 14x 14x 14x 14683x 14683x 12x 12x 29x 11x 11x 29x 1x 1x 14683x 14683x | import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import * as Expressions from "../abap/2_statements/expressions";
import * as Statements from "../abap/2_statements/statements";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {ABAPObject} from "../objects/_abap_object";
import {StatementNode} from "../abap/nodes";
import {SyntaxLogic} from "../abap/5_syntax/syntax";
import {ISyntaxResult} from "../abap/5_syntax/_spaghetti_scope";
import {TypedIdentifier} from "../abap/types/_typed_identifier";
import {GenericObjectReferenceType, ObjectReferenceType, StructureType, VoidType} from "../abap/types/basic";
import {AbstractType} from "../abap/types/basic/_abstract_type";
import {Position} from "../position";
import {Dash, InstanceArrow, StaticArrow} from "../abap/1_lexer/tokens";
export class UseMessageClassConf extends BasicRuleConfig {
/** Allow MESSAGE with an object reference, eg. an exception, like MESSAGE lx_error TYPE 'E' */
public allowExceptions: boolean = true;
}
export class UseMessageClass extends ABAPRule {
private conf = new UseMessageClassConf();
public getMetadata(): IRuleMetadata {
return {
key: "use_message_class",
title: "Use message class",
shortDescription: `Define messages in SE91 instead of using inline text or variables`,
extendedInformation: `If allowExceptions is enabled, MESSAGE with an object reference is allowed, eg. MESSAGE lx_error TYPE 'E'.
Void types are only allowed if the type name contains "CX_", as void class references cannot be distinguished from other void types.`,
tags: [RuleTag.Styleguide, RuleTag.SingleFile],
badExample: `MESSAGE 'Something went wrong' TYPE 'E'.`,
goodExample: `MESSAGE e001(bc_msg).`,
};
}
public getConfig() {
return this.conf;
}
public setConfig(conf: UseMessageClassConf) {
this.conf = conf;
}
public runParsed(file: ABAPFile, obj: ABAPObject): Issue[] {
const structure = file.getStructure();
if (structure === undefined) {
return [];
}
const issues: Issue[] = [];
const key = this.getMetadata().key;
let syntax: ISyntaxResult | undefined = undefined;
for (const statement of structure.findAllStatements(Statements.Message)) {
// The parser populates MessageSource only for the class-based forms (e001(bc_msg) or ID/TYPE/NUMBER).
// The text form (MESSAGE <source> TYPE <source>) uses MessageSourceSource directly with no MessageSource child.
// Absence of MessageSource is therefore a reliable discriminator between the two forms.
if (statement.findDirectExpression(Expressions.MessageSource) !== undefined) {
continue;
}
if (this.conf.allowExceptions === true) {
syntax ??= new SyntaxLogic(this.reg, obj).run();
if (this.isObjectReference(statement, file, syntax)) {
continue;
}
}
issues.push(Issue.atStatement(file, statement, "Use a message class (SE91) instead of inline text or variable", key, this.conf.severity));
}
return issues;
}
private isObjectReference(statement: StatementNode, file: ABAPFile, syntax: ISyntaxResult): boolean {
const chain = statement.findDirectExpression(Expressions.MessageSourceSource)
?.findDirectExpression(Expressions.Source)?.findDirectExpression(Expressions.FieldChain);
if (chain === undefined) {
return false;
}
let type = undefined as AbstractType | undefined;
for (const child of chain.getChildren()) {
const c = child.get();
if (c instanceof Expressions.ComponentName) {
type = type instanceof StructureType ? type.getComponentByName(child.concatTokens()) : undefined;
} else if (c instanceof Expressions.SourceField
|| c instanceof Expressions.SourceFieldSymbol
|| c instanceof Expressions.AttributeName) {
type = this.findType(child.getFirstToken().getStart(), file, syntax);
} else if (c instanceof Expressions.ClassName
|| c instanceof InstanceArrow
|| c instanceof StaticArrow
|| c instanceof Dash) {
continue;
} else {
return false;
}
if (type === undefined) {
return false;
}
}
// void class references cannot be distinguished from other void types, guess based on the name
return type instanceof ObjectReferenceType
|| type instanceof GenericObjectReferenceType
|| (type instanceof VoidType && /CX_/i.test(type.getVoided() || ""));
}
private findType(start: Position, file: ABAPFile, syntax: ISyntaxResult): AbstractType | undefined {
const scope = syntax.spaghetti.lookupPosition(start, file.getFilename());
for (const r of scope?.getData().references || []) {
if (r.position.getStart().equals(start) && r.resolved instanceof TypedIdentifier) {
return r.resolved.getType();
}
}
return undefined;
}
}
|