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 144 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 23430x 23430x 23430x 23430x 23430x 23430x 23430x 23430x 1x 11716x 11716x 11716x 11716x 11716x 57386x 57386x 57386x 57386x 57386x 57386x 57386x 57386x 57386x 57386x 57386x 57386x 11716x 11716x 11175x 11175x 11716x 11716x 256x 256x 11716x 11716x 299x 299x 299x 299x 299x 299x 299x 299x 148x 85x 85x 148x 4x 4x 59x 59x 148x 2x 2x 2x 2x 2x 2x 2x 148x 299x 299x 68x 39x 39x 29x 29x 68x 68x 299x 299x 50x 1x 1x 49x 50x 49x 50x 60x 7x 7x 7x 7x 60x 31x 31x 31x 31x 60x 60x 60x 49x 49x 299x 299x 11716x 11716x 49x 49x 49x 49x 49x 7x 7x 7x 49x 49x 49x 49x 18x 29x 29x 29x 18x 18x 11x 11x 18x 49x 49x 11716x | import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Issue} from "../issue";
import {Visibility} from "../abap/4_file_information/visibility";
import {InfoMethodDefinition} from "../abap/4_file_information/_abap_file_information";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {Position} from "../position";
export class AbapdocConf extends BasicRuleConfig {
/** Check local classes and interfaces for abapdoc. */
public checkLocal: boolean = false;
public classDefinition: boolean = false;
public interfaceDefinition: boolean = false;
/** Ignores classes flagged as FOR TESTING */
public ignoreTestClasses: boolean = true;
}
export class Abapdoc extends ABAPRule {
private conf = new AbapdocConf();
public getMetadata(): IRuleMetadata {
return {
key: "abapdoc",
title: "Check abapdoc",
shortDescription: `Various checks regarding abapdoc.`,
extendedInformation: `Base rule checks for existence of abapdoc for public class methods and all interface methods.
Plus class and interface definitions.
https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#abap-doc-only-for-public-apis`,
tags: [RuleTag.SingleFile, RuleTag.Styleguide],
};
}
public getConfig() {
return this.conf;
}
public setConfig(conf: AbapdocConf): void {
this.conf = conf;
}
public runParsed(file: ABAPFile) {
const issues: Issue[] = [];
const rows = file.getRawRows();
const regexEmptyTags = '^\\"! .*<[^/>]*><\\/';
const regexEmptyAbapdoc = '^\\"!.+$';
const regexEmptyParameterName = '^\\"! @parameter .+\\|';
let methods: InfoMethodDefinition[] = [];
for (const classDef of file.getInfo().listClassDefinitions()) {
if (this.conf.checkLocal === false && classDef.isLocal === true) {
continue;
}
if (this.conf.ignoreTestClasses === true && classDef.isForTesting === true) {
continue;
}
methods = methods.concat(classDef.methods.filter(m => m.visibility === Visibility.Public));
if (this.conf.classDefinition === true) {
const previousRow = classDef.identifier.getStart().getRow() - 2;
if (rows[previousRow]?.trim().substring(0, 2) !== "\"!") {
const message = "Missing ABAP Doc for class " + classDef.identifier.getToken().getStr();
const issue = Issue.atIdentifier(classDef.identifier, message, this.getMetadata().key, this.conf.severity);
issues.push(issue);
}
}
}
for (const interfaceDef of file.getInfo().listInterfaceDefinitions()) {
if (this.conf.checkLocal === false && interfaceDef.isLocal === true) {
continue;
}
methods = methods.concat(interfaceDef.methods);
if (this.conf.interfaceDefinition === true) {
const previousRow = interfaceDef.identifier.getStart().getRow() - 2;
if (rows[previousRow]?.trim().substring(0, 2) !== "\"!") {
const message = "Missing ABAP Doc for interface " + interfaceDef.identifier.getToken().getStr();
const issue = Issue.atIdentifier(interfaceDef.identifier, message, this.getMetadata().key, this.conf.severity);
issues.push(issue);
}
}
}
for (const method of methods) {
if (method.isRedefinition === true) {
continue;
}
const previousRowsTexts = this.getAbapdoc(rows, method.identifier.getStart());
if (previousRowsTexts === undefined) {
continue;
}
for (const rowText of previousRowsTexts) {
if (rowText.trim().match(regexEmptyTags) !== null) {
const message = "Empty tag(s) in ABAP Doc for method " + method.identifier.getToken().getStr() + " (" + rowText + ")";
const issue = Issue.atIdentifier(method.identifier, message, this.getMetadata().key, this.conf.severity);
issues.push(issue);
}
if (rowText.trim().match(regexEmptyAbapdoc) === null && previousRowsTexts.indexOf(rowText) === previousRowsTexts.length - 1) {
const message = "Missing ABAP Doc for method " + method.identifier.getToken().getStr();
const issue = Issue.atIdentifier(method.identifier, message, this.getMetadata().key, this.conf.severity);
issues.push(issue);
}
if (rowText.trim().match(regexEmptyParameterName) !== null) {
const message = "Missing ABAP Doc parameter name for method " + method.identifier.getToken().getStr();
const issue = Issue.atIdentifier(method.identifier, message, this.getMetadata().key, this.conf.severity);
issues.push(issue);
}
}
}
return issues;
}
private getAbapdoc(rows: readonly string[], pos: Position): string[] {
let previousRow = pos.getRow() - 2;
let rowText = rows[previousRow].trim().toUpperCase();
const text: string[] = [];
if (rowText === "METHODS" || rowText === "CLASS-METHODS") {
previousRow--;
rowText = rows[previousRow].trim().toUpperCase();
}
text.push(rowText);
//we need to push the first row despite if it is actually an abapdoc or not
//if the first row above a method is abapdoc then try to get the rest of the abapdoc block above
if (rowText.trim().substring(0, 2) === "\"!") {
while (previousRow >= 0) {
previousRow--;
rowText = rows[previousRow].trim().toUpperCase();
if (rowText.trim().substring(0, 2) !== "\"!") {
break;
}
text.push(rowText);
}
}
return text;
}
} |