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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14712x 14712x 14712x 14712x 14712x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 58047x 14712x 14712x 14120x 14120x 14712x 14712x 264x 264x 14712x 14712x 327x 327x 327x 327x 327x 1921x 1921x 1921x 1921x 225x 225x 1921x 380x 380x 1696x 1268x 1316x 48x 6x 6x 42x 42x 42x 1921x 54x 42x 42x 42x 54x 1921x 1921x 42x 42x 3x 3x 39x 39x 39x 1921x 1921x 1921x 1921x 1921x 12x 1x 12x 3x 3x 3x 3x 3x 11x 2x 2x 2x 6x 6x 1921x 5x 5x 5x 39x 39x 1921x 10x 1921x 9x 9x 1921x 19x 19x 1921x 327x 327x 327x 14712x 14712x 1375x 1375x 1375x 14712x 14712x 39x 39x 14712x 14712x 39x 39x 39x 39x 39x 39x 4x 4x 35x 35x 35x 39x 27x 1x 1x 26x 26x 8x 39x 4x 4x 4x 4x 39x 1x 1x 3x 3x 3x 14712x 14712x | import * as Statements from "../abap/2_statements/statements";
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {Comment} from "../abap/2_statements/statements/_statement";
import {StatementNode} from "../abap/nodes";
import {EditHelper, IEdit} from "../edit_helper";
import {Position} from "../position";
export class WrongAbapdocPositionConf extends BasicRuleConfig {
}
export class WrongAbapdocPosition extends ABAPRule {
private conf = new WrongAbapdocPositionConf();
public getMetadata(): IRuleMetadata {
return {
key: "wrong_abapdoc_position",
title: "Wrong ABAP Doc position",
shortDescription: `ABAP Doc must be placed directly in front of the declaration it documents.`,
extendedInformation: `ABAP Doc documents the single declaration following it, an ABAP Doc block placed
elsewhere is silently ignored, leaving the declaration undocumented.
The following positions are reported,
* in front of a chained keyword, ie. before "CONSTANTS:" instead of after the colon,
* in the middle of a statement, ie. between the parameters of a METHODS definition,
* directly in front of ENDCLASS, ENDINTERFACE or a SECTION statement,
* separated from its declaration by a blank line or a normal comment, the system reads both as the end of the block.
Only checks ABAP Doc inside class definitions and interfaces, and directly in front of them.
The quick fix for a blank line deletes the blank lines.`,
tags: [RuleTag.SingleFile, RuleTag.Quickfix],
badExample: `CLASS zcl_foo DEFINITION PUBLIC.
PUBLIC SECTION.
"! Navigation modes
CONSTANTS:
BEGIN OF cs_nav_mode,
back TYPE i VALUE 1,
END OF cs_nav_mode.
ENDCLASS.`,
goodExample: `CLASS zcl_foo DEFINITION PUBLIC.
PUBLIC SECTION.
CONSTANTS:
"! Navigation modes
BEGIN OF cs_nav_mode,
back TYPE i VALUE 1,
END OF cs_nav_mode.
ENDCLASS.`,
};
}
public getConfig() {
return this.conf;
}
public setConfig(conf: WrongAbapdocPositionConf) {
this.conf = conf;
}
public runParsed(file: ABAPFile): Issue[] {
const issues: Issue[] = [];
const statements = file.getStatements();
let definition = false;
for (let i = 0; i < statements.length; i++) {
const statement = statements[i];
const type = statement.get();
if (type instanceof Statements.ClassDefinition || type instanceof Statements.Interface) {
definition = true;
continue;
} else if (type instanceof Statements.EndClass || type instanceof Statements.EndInterface) {
definition = false;
continue;
} else if (this.isAbapdoc(statement) === false) {
continue;
} else if (this.isAbapdoc(statements[i - 1]) === true
&& statements[i - 1].getStart().getRow() + 1 === statement.getStart().getRow()) {
continue; // only report the first row of each block
}
let next: StatementNode | undefined = undefined;
let j = i + 1;
for (; j < statements.length; j++) {
if (!(statements[j].get() instanceof Comment)) {
next = statements[j];
break;
}
}
if (next === undefined) {
continue;
} else if (definition === false
&& !(next.get() instanceof Statements.ClassDefinition)
&& !(next.get() instanceof Statements.Interface)) {
continue;
}
// comments inside "next" are listed in front of it, only look at the rows above it
const nextRow = this.startRow(next);
const trailing = statements[i - 1]?.getEnd().getRow() === statement.getStart().getRow();
let last = statement;
let separator: string | undefined = undefined;
let fix: IEdit | undefined = undefined;
for (const s of trailing ? [] : statements.slice(i + 1, j)) {
if (s.getStart().getRow() >= nextRow) {
break;
} else if (s.getStart().getRow() > last.getEnd().getRow() + 1) {
separator = "a blank line";
if (this.isAbapdoc(s) === true) {
fix = EditHelper.deleteRange(file, new Position(last.getEnd().getRow() + 1, 1), new Position(s.getStart().getRow(), 1));
}
break;
} else if (this.isAbapdoc(s) === false) {
separator = "a comment";
break;
}
last = s;
}
if (separator === undefined && trailing === false && nextRow > last.getEnd().getRow() + 1) {
separator = "a blank line";
fix = EditHelper.deleteRange(file, new Position(last.getEnd().getRow() + 1, 1), new Position(nextRow, 1));
}
let message = this.check(statement, next);
if (message !== undefined) {
fix = undefined;
} else if (separator !== undefined) {
message = "ABAP Doc is separated from its declaration by " + separator;
}
if (message !== undefined) {
issues.push(Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, fix));
}
}
return issues;
}
private isAbapdoc(statement: StatementNode | undefined): boolean {
return statement?.get() instanceof Comment
&& statement.getFirstToken().getStr().startsWith(`"!`);
}
private startRow(statement: StatementNode): number {
return Math.min(statement.getStart().getRow(), ...statement.getPragmas().map(p => p.getRow()));
}
private check(abapdoc: StatementNode, next: StatementNode): string | undefined {
const type = next.get();
if (type instanceof Statements.EndClass
|| type instanceof Statements.EndInterface
|| type instanceof Statements.Public
|| type instanceof Statements.Protected
|| type instanceof Statements.Private) {
return "ABAP Doc does not document anything, move or delete it";
}
const position = abapdoc.getStart();
const colon = next.getColon();
if (colon === undefined) {
if (position.isAfter(next.getStart()) === true) {
return "ABAP Doc inside statement, move it in front of the statement";
}
return undefined;
}
if (position.isBefore(colon.getStart()) === true) {
return "ABAP Doc in front of chained statement, move it after the colon";
}
const member = next.getTokens().find(t => t.getStart().isAfter(colon.getStart()));
if (member !== undefined && position.isAfter(member.getStart()) === true) {
return "ABAP Doc inside statement, move it in front of the chained declaration";
}
return undefined;
}
}
|