-
Notifications
You must be signed in to change notification settings - Fork 70
/
abapdoc.ts
144 lines (125 loc) · 5.5 KB
/
abapdoc.ts
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
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() + " (" + rowText + ")";
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() + " (" + rowText + ")";
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;
}
}