@abaplint/core 2.120.73 → 2.120.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/abaplint.d.ts +6 -1
- package/build/src/abap/2_statements/combi.js +143 -51
- package/build/src/abap/2_statements/expressions/method_param_optional.js +3 -1
- package/build/src/abap/5_syntax/basic_types.js +67 -23
- package/build/src/abap/5_syntax/statements/append.js +6 -0
- package/build/src/abap/types/method_definition.js +22 -0
- package/build/src/abap/types/method_parameters.js +57 -0
- package/build/src/ddic.js +1 -1
- package/build/src/objects/data_definition.js +9 -1
- package/build/src/objects/domain.js +4 -1
- package/build/src/registry.js +1 -1
- package/build/src/rules/abapdoc_html_tag.js +123 -0
- package/build/src/rules/abapdoc_leading_at.js +117 -0
- package/build/src/rules/abapdoc_parameter_names.js +149 -0
- package/build/src/rules/cds_check_syntax.js +4 -4
- package/build/src/rules/change_if_to_case.js +3 -2
- package/build/src/rules/changing_sorted_line.js +235 -0
- package/build/src/rules/class_attribute_names.js +15 -6
- package/build/src/rules/delete_index_in_loop.js +163 -0
- package/build/src/rules/final_newline.js +78 -0
- package/build/src/rules/forbidden_identifier.js +4 -3
- package/build/src/rules/forbidden_void_type.js +4 -9
- package/build/src/rules/index.js +11 -0
- package/build/src/rules/inline_packed_computation.js +220 -0
- package/build/src/rules/intf_referencing_clas.js +3 -1
- package/build/src/rules/line_only_punc.js +1 -1
- package/build/src/rules/local_class_naming.js +15 -2
- package/build/src/rules/local_variable_names.js +18 -9
- package/build/src/rules/macro_naming.js +3 -2
- package/build/src/rules/method_parameter_names.js +17 -7
- package/build/src/rules/modify_only_own_db_tables.js +3 -2
- package/build/src/rules/no_exclamation_escape.js +32 -2
- package/build/src/rules/no_prefixes.js +25 -12
- package/build/src/rules/object_naming.js +18 -3
- package/build/src/rules/prefer_inline_cast.js +276 -0
- package/build/src/rules/range_row_values.js +200 -0
- package/build/src/rules/selection_screen_naming.js +21 -2
- package/build/src/rules/sicf_consistency.js +1 -2
- package/build/src/rules/sort_empty_key.js +188 -0
- package/build/src/rules/subrc_after_assign.js +220 -0
- package/build/src/rules/types_naming.js +3 -2
- package/build/src/rules/wrong_abapdoc_position.js +58 -11
- package/build/src/rules/xml_consistency.js +6 -2
- package/build/src/utils/name_validator.js +7 -3
- package/package.json +1 -1
|
@@ -47,6 +47,12 @@ const _reference_1 = require("../5_syntax/_reference");
|
|
|
47
47
|
const identifier_1 = require("../1_lexer/tokens/identifier");
|
|
48
48
|
const _scope_type_1 = require("../5_syntax/_scope_type");
|
|
49
49
|
const _syntax_input_1 = require("../5_syntax/_syntax_input");
|
|
50
|
+
// the keywords that can follow a parameter in each section, a plain parameter name is read as them
|
|
51
|
+
const KEYWORDS_AFTER = {
|
|
52
|
+
"IMPORTING": ["PREFERRED", "EXPORTING", "CHANGING", "RETURNING", "RAISING", "EXCEPTIONS"],
|
|
53
|
+
"EXPORTING": ["CHANGING", "RETURNING", "RAISING", "EXCEPTIONS"],
|
|
54
|
+
"CHANGING": ["RETURNING", "RAISING", "EXCEPTIONS"],
|
|
55
|
+
};
|
|
50
56
|
// todo:
|
|
51
57
|
// this.exceptions = [];
|
|
52
58
|
// also consider RAISING vs EXCEPTIONS
|
|
@@ -291,6 +297,7 @@ class MethodParameters {
|
|
|
291
297
|
}
|
|
292
298
|
add(target, source, input, meta, abstractMethod) {
|
|
293
299
|
var _a;
|
|
300
|
+
this.checkKeywordNames(source, input);
|
|
294
301
|
for (const opt of source.findAllExpressions(Expressions.MethodParamOptional)) {
|
|
295
302
|
const p = opt.findDirectExpression(Expressions.MethodParam);
|
|
296
303
|
if (p === undefined) {
|
|
@@ -331,6 +338,56 @@ class MethodParameters {
|
|
|
331
338
|
target.push(method_param_1.MethodParam.runSyntax(param, input, [...meta, ...extraMeta]));
|
|
332
339
|
}
|
|
333
340
|
}
|
|
341
|
+
// A system reads a plain parameter name as a keyword wherever that keyword can stand, measured on 758:
|
|
342
|
+
// "val TYPE clike default TYPE i" makes default the DEFAULT addition of val, and then fails
|
|
343
|
+
// with "Unable to interpret ..." on what follows. "!default" and "VALUE(default)" are names
|
|
344
|
+
checkKeywordNames(section, input) {
|
|
345
|
+
var _a;
|
|
346
|
+
const following = KEYWORDS_AFTER[section.getFirstToken().getStr().toUpperCase()];
|
|
347
|
+
if (following === undefined) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// undefined: no parameter before in this section, true: the one before has OPTIONAL or DEFAULT
|
|
351
|
+
let previousHasAddition = undefined;
|
|
352
|
+
for (const child of section.getChildren()) {
|
|
353
|
+
if (!(child instanceof nodes_1.ExpressionNode)) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
let param = undefined;
|
|
357
|
+
let optional = false;
|
|
358
|
+
if (child.get() instanceof Expressions.MethodParamOptional) {
|
|
359
|
+
param = child.findDirectExpression(Expressions.MethodParam);
|
|
360
|
+
optional = child.findDirectTokenByText("OPTIONAL") !== undefined;
|
|
361
|
+
}
|
|
362
|
+
else if (child.get() instanceof Expressions.MethodParam) {
|
|
363
|
+
param = child;
|
|
364
|
+
}
|
|
365
|
+
else if (child.get() instanceof Expressions.MethodParamName) {
|
|
366
|
+
// "TYPE ANY STRUCTURE", not measured, so never reported after it
|
|
367
|
+
previousHasAddition = true;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (param === undefined) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const first = param.getFirstToken();
|
|
374
|
+
const name = first.getStr().toUpperCase();
|
|
375
|
+
if (((_a = param.getChildren()[0]) === null || _a === void 0 ? void 0 : _a.get()) instanceof Expressions.MethodParamName) {
|
|
376
|
+
let readAs = undefined;
|
|
377
|
+
if (following.includes(name)) {
|
|
378
|
+
readAs = "the " + (name === "PREFERRED" ? "PREFERRED PARAMETER" : name) + " addition";
|
|
379
|
+
}
|
|
380
|
+
else if ((name === "DEFAULT" || name === "OPTIONAL") && previousHasAddition === false) {
|
|
381
|
+
readAs = "the " + name + " addition of the parameter before it";
|
|
382
|
+
}
|
|
383
|
+
if (readAs !== undefined) {
|
|
384
|
+
const message = "Parameter \"" + first.getStr() + "\" is read as " + readAs + ", rename it or escape it as !" + first.getStr();
|
|
385
|
+
input.issues.push((0, _syntax_input_1.syntaxIssue)(input, first, message));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
previousHasAddition = optional || param.findFirstExpression(Expressions.Default) !== undefined;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
334
391
|
isPassByValue(param) {
|
|
335
392
|
var _a;
|
|
336
393
|
// "!" is the identifier escape, generated code has eg. "IMPORTING !VALUE(iv_foo)"
|
package/build/src/ddic.js
CHANGED
|
@@ -256,7 +256,7 @@ class DDIC {
|
|
|
256
256
|
lookupDomain(name, dataElement, description) {
|
|
257
257
|
const found = this.reg.getObject("DOMA", name);
|
|
258
258
|
if (found) {
|
|
259
|
-
return { type: found.
|
|
259
|
+
return { type: found.parseDomainType(this.reg, dataElement, description), object: found };
|
|
260
260
|
}
|
|
261
261
|
else if (this.reg.inErrorNamespace(name)) {
|
|
262
262
|
return { type: new Types.UnknownType(name + ", lookupDomain"), object: undefined };
|
|
@@ -11,6 +11,8 @@ class DataDefinition extends _abstract_object_1.AbstractObject {
|
|
|
11
11
|
super(...arguments);
|
|
12
12
|
this.parserError = undefined;
|
|
13
13
|
this.parsedData = undefined;
|
|
14
|
+
this.typeCache = undefined;
|
|
15
|
+
this.typeCacheData = undefined;
|
|
14
16
|
}
|
|
15
17
|
getType() {
|
|
16
18
|
return "DDLS";
|
|
@@ -38,7 +40,11 @@ class DataDefinition extends _abstract_object_1.AbstractObject {
|
|
|
38
40
|
}
|
|
39
41
|
parseType(reg) {
|
|
40
42
|
this.parse();
|
|
41
|
-
|
|
43
|
+
if (this.typeCache === undefined || this.typeCacheData !== this.parsedData) {
|
|
44
|
+
this.typeCacheData = this.parsedData;
|
|
45
|
+
this.typeCache = new cds_determine_types_1.CDSDetermineTypes().parseType(reg, this.parsedData, this.getName());
|
|
46
|
+
}
|
|
47
|
+
return this.typeCache;
|
|
42
48
|
}
|
|
43
49
|
getParsedData() {
|
|
44
50
|
return this.parsedData;
|
|
@@ -50,6 +56,8 @@ class DataDefinition extends _abstract_object_1.AbstractObject {
|
|
|
50
56
|
}
|
|
51
57
|
setDirty() {
|
|
52
58
|
this.parsedData = undefined;
|
|
59
|
+
this.typeCache = undefined;
|
|
60
|
+
this.typeCacheData = undefined;
|
|
53
61
|
this.parserError = undefined;
|
|
54
62
|
super.setDirty();
|
|
55
63
|
}
|
|
@@ -81,7 +81,10 @@ class Domain extends _abstract_object_1.AbstractObject {
|
|
|
81
81
|
this.parsedXML = undefined;
|
|
82
82
|
super.setDirty();
|
|
83
83
|
}
|
|
84
|
-
parseType(reg
|
|
84
|
+
parseType(reg) {
|
|
85
|
+
return this.parseDomainType(reg);
|
|
86
|
+
}
|
|
87
|
+
parseDomainType(reg, dataElement, description) {
|
|
85
88
|
// dont cache the DOMA parsed type, they are cached on DTEL level
|
|
86
89
|
// also note that the type carries the name of the DTEL
|
|
87
90
|
if (this.parsedXML === undefined) {
|
package/build/src/registry.js
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AbapdocHtmlTag = exports.AbapdocHtmlTagConf = void 0;
|
|
37
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
38
|
+
const issue_1 = require("../issue");
|
|
39
|
+
const _abap_rule_1 = require("./_abap_rule");
|
|
40
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
41
|
+
const _irule_1 = require("./_irule");
|
|
42
|
+
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
43
|
+
const position_1 = require("../position");
|
|
44
|
+
class AbapdocHtmlTagConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
45
|
+
}
|
|
46
|
+
exports.AbapdocHtmlTagConf = AbapdocHtmlTagConf;
|
|
47
|
+
const ALLOWED = new Set(["p", "em", "strong", "ul", "ol", "li", "br", "h1", "h2", "h3"]);
|
|
48
|
+
class AbapdocHtmlTag extends _abap_rule_1.ABAPRule {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(...arguments);
|
|
51
|
+
this.conf = new AbapdocHtmlTagConf();
|
|
52
|
+
}
|
|
53
|
+
getMetadata() {
|
|
54
|
+
return {
|
|
55
|
+
key: "abapdoc_html_tag",
|
|
56
|
+
title: "Unsupported HTML tag in ABAP Doc",
|
|
57
|
+
shortDescription: `Reports tag-like tokens in ABAP Doc which are not ABAP Doc HTML tags.`,
|
|
58
|
+
extendedInformation: `ABAP Doc is parsed as HTML, a field symbol or a placeholder written as <name> is an
|
|
59
|
+
unsupported and unclosed tag, the system warns and the rendered documentation drops the word.
|
|
60
|
+
|
|
61
|
+
Supported tags are p, em, strong, ul, ol, li, br, h1, h2 and h3, also as closing tags and with attributes.
|
|
62
|
+
|
|
63
|
+
Write <name> to show the angle brackets. A "<" which is not followed by a letter, like "a < b", is not reported.
|
|
64
|
+
|
|
65
|
+
Only checks ABAP Doc inside class definitions and interfaces.`,
|
|
66
|
+
tags: [_irule_1.RuleTag.SingleFile],
|
|
67
|
+
badExample: `INTERFACE zif_foo PUBLIC.
|
|
68
|
+
"! Returns the <name> of the selected row
|
|
69
|
+
METHODS name RETURNING VALUE(result) TYPE string.
|
|
70
|
+
ENDINTERFACE.`,
|
|
71
|
+
goodExample: `INTERFACE zif_foo PUBLIC.
|
|
72
|
+
"! Returns the <name> of the selected row
|
|
73
|
+
METHODS name RETURNING VALUE(result) TYPE string.
|
|
74
|
+
ENDINTERFACE.`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
getConfig() {
|
|
78
|
+
return this.conf;
|
|
79
|
+
}
|
|
80
|
+
setConfig(conf) {
|
|
81
|
+
this.conf = conf;
|
|
82
|
+
}
|
|
83
|
+
runParsed(file) {
|
|
84
|
+
const issues = [];
|
|
85
|
+
let definition = false;
|
|
86
|
+
for (const statement of file.getStatements()) {
|
|
87
|
+
const type = statement.get();
|
|
88
|
+
if (type instanceof Statements.ClassDefinition || type instanceof Statements.Interface) {
|
|
89
|
+
definition = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
else if (type instanceof Statements.EndClass || type instanceof Statements.EndInterface) {
|
|
93
|
+
definition = false;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
else if (definition === false || !(type instanceof _statement_1.Comment)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const token = statement.getFirstToken();
|
|
100
|
+
const str = token.getStr();
|
|
101
|
+
if (str.startsWith(`"!`) === false) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const regex = /<\/?([a-zA-Z][\w-]*)(\s[^<>]*)?\/?>/g;
|
|
105
|
+
let match;
|
|
106
|
+
while ((match = regex.exec(str)) !== null) {
|
|
107
|
+
const name = match[1];
|
|
108
|
+
if (ALLOWED.has(name.toLowerCase())) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const row = token.getStart().getRow();
|
|
112
|
+
const col = token.getStart().getCol() + match.index;
|
|
113
|
+
const start = new position_1.Position(row, col);
|
|
114
|
+
const end = new position_1.Position(row, col + match[0].length);
|
|
115
|
+
const message = `HTML tag <${name}> is not supported in ABAP Doc, write <${name}>`;
|
|
116
|
+
issues.push(issue_1.Issue.atRange(file, start, end, message, this.getMetadata().key, this.conf.severity));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return issues;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.AbapdocHtmlTag = AbapdocHtmlTag;
|
|
123
|
+
//# sourceMappingURL=abapdoc_html_tag.js.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AbapdocLeadingAt = exports.AbapdocLeadingAtConf = void 0;
|
|
37
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
38
|
+
const issue_1 = require("../issue");
|
|
39
|
+
const _abap_rule_1 = require("./_abap_rule");
|
|
40
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
41
|
+
const _irule_1 = require("./_irule");
|
|
42
|
+
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
43
|
+
class AbapdocLeadingAtConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
44
|
+
}
|
|
45
|
+
exports.AbapdocLeadingAtConf = AbapdocLeadingAtConf;
|
|
46
|
+
class AbapdocLeadingAt extends _abap_rule_1.ABAPRule {
|
|
47
|
+
constructor() {
|
|
48
|
+
super(...arguments);
|
|
49
|
+
this.conf = new AbapdocLeadingAtConf();
|
|
50
|
+
}
|
|
51
|
+
getMetadata() {
|
|
52
|
+
return {
|
|
53
|
+
key: "abapdoc_leading_at",
|
|
54
|
+
title: "ABAP Doc line starting with @",
|
|
55
|
+
shortDescription: `Reports ABAP Doc lines starting with "@" which is not followed by a command.`,
|
|
56
|
+
extendedInformation: `In ABAP Doc, "@" at the start of a line introduces a command, the commands are
|
|
57
|
+
@parameter, @raising and @exception. Any other text after a leading "@", like a CDS annotation,
|
|
58
|
+
gives the syntax warning 'A command was expected after ABAP Doc symbol "@"'.
|
|
59
|
+
|
|
60
|
+
An "@" in the middle of a line is plain text and not reported.
|
|
61
|
+
|
|
62
|
+
Only checks ABAP Doc inside class definitions and interfaces.`,
|
|
63
|
+
tags: [_irule_1.RuleTag.SingleFile],
|
|
64
|
+
badExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
65
|
+
PUBLIC SECTION.
|
|
66
|
+
"! the first of
|
|
67
|
+
"! @UI.presentationVariant sortOrder
|
|
68
|
+
DATA mv_sort_field TYPE string.
|
|
69
|
+
ENDCLASS.`,
|
|
70
|
+
goodExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
71
|
+
PUBLIC SECTION.
|
|
72
|
+
"! the first of @UI.presentationVariant
|
|
73
|
+
"! sortOrder
|
|
74
|
+
DATA mv_sort_field TYPE string.
|
|
75
|
+
ENDCLASS.`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
getConfig() {
|
|
79
|
+
return this.conf;
|
|
80
|
+
}
|
|
81
|
+
setConfig(conf) {
|
|
82
|
+
this.conf = conf;
|
|
83
|
+
}
|
|
84
|
+
runParsed(file) {
|
|
85
|
+
const issues = [];
|
|
86
|
+
let definition = false;
|
|
87
|
+
for (const statement of file.getStatements()) {
|
|
88
|
+
const type = statement.get();
|
|
89
|
+
if (type instanceof Statements.ClassDefinition || type instanceof Statements.Interface) {
|
|
90
|
+
definition = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
else if (type instanceof Statements.EndClass || type instanceof Statements.EndInterface) {
|
|
94
|
+
definition = false;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
else if (definition === false || !(type instanceof _statement_1.Comment)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const token = statement.getFirstToken();
|
|
101
|
+
const str = token.getStr();
|
|
102
|
+
if (str.startsWith(`"!`) === false) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const text = str.substring(2).trimStart();
|
|
106
|
+
if (text.startsWith("@") === false
|
|
107
|
+
|| text.match(/^@(parameter|raising|exception)(\s|$)/i) !== null) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const message = `A command was expected after ABAP Doc symbol "@", move the "@" away from the start of the line`;
|
|
111
|
+
issues.push(issue_1.Issue.atToken(file, token, message, this.getMetadata().key, this.conf.severity));
|
|
112
|
+
}
|
|
113
|
+
return issues;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.AbapdocLeadingAt = AbapdocLeadingAt;
|
|
117
|
+
//# sourceMappingURL=abapdoc_leading_at.js.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AbapdocParameterNames = exports.AbapdocParameterNamesConf = void 0;
|
|
37
|
+
const Statements = __importStar(require("../abap/2_statements/statements"));
|
|
38
|
+
const Expressions = __importStar(require("../abap/2_statements/expressions"));
|
|
39
|
+
const issue_1 = require("../issue");
|
|
40
|
+
const _abap_rule_1 = require("./_abap_rule");
|
|
41
|
+
const _basic_rule_config_1 = require("./_basic_rule_config");
|
|
42
|
+
const _irule_1 = require("./_irule");
|
|
43
|
+
const _statement_1 = require("../abap/2_statements/statements/_statement");
|
|
44
|
+
class AbapdocParameterNamesConf extends _basic_rule_config_1.BasicRuleConfig {
|
|
45
|
+
}
|
|
46
|
+
exports.AbapdocParameterNamesConf = AbapdocParameterNamesConf;
|
|
47
|
+
class AbapdocParameterNames extends _abap_rule_1.ABAPRule {
|
|
48
|
+
constructor() {
|
|
49
|
+
super(...arguments);
|
|
50
|
+
this.conf = new AbapdocParameterNamesConf();
|
|
51
|
+
}
|
|
52
|
+
getMetadata() {
|
|
53
|
+
return {
|
|
54
|
+
key: "abapdoc_parameter_names",
|
|
55
|
+
title: "ABAP Doc parameter names",
|
|
56
|
+
shortDescription: `Checks that the parameters documented in ABAP Doc exist in the method definition.`,
|
|
57
|
+
extendedInformation: `Reports "! @parameter lines in the ABAP Doc block in front of a METHODS or CLASS-METHODS
|
|
58
|
+
definition when the parameter is not a parameter of the method, typically after a parameter was renamed or removed.
|
|
59
|
+
Also reports a parameter documented twice.
|
|
60
|
+
|
|
61
|
+
Missing @parameter lines are not reported, documenting parameters is optional. @raising and @exception lines are not checked.
|
|
62
|
+
|
|
63
|
+
Only the block directly in front of the method is checked, a blank line or a plain comment ends the block,
|
|
64
|
+
see rule wrong_abapdoc_position for misplaced ABAP Doc.`,
|
|
65
|
+
tags: [_irule_1.RuleTag.SingleFile],
|
|
66
|
+
badExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
67
|
+
PUBLIC SECTION.
|
|
68
|
+
"! @parameter iv_bar | Bar
|
|
69
|
+
METHODS foo IMPORTING iv_foo TYPE i.
|
|
70
|
+
ENDCLASS.`,
|
|
71
|
+
goodExample: `CLASS zcl_foo DEFINITION PUBLIC.
|
|
72
|
+
PUBLIC SECTION.
|
|
73
|
+
"! @parameter iv_foo | Foo
|
|
74
|
+
METHODS foo IMPORTING iv_foo TYPE i.
|
|
75
|
+
ENDCLASS.`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
getConfig() {
|
|
79
|
+
return this.conf;
|
|
80
|
+
}
|
|
81
|
+
setConfig(conf) {
|
|
82
|
+
this.conf = conf;
|
|
83
|
+
}
|
|
84
|
+
runParsed(file) {
|
|
85
|
+
var _a;
|
|
86
|
+
const issues = [];
|
|
87
|
+
const statements = file.getStatements();
|
|
88
|
+
for (let i = 0; i < statements.length; i++) {
|
|
89
|
+
const def = statements[i];
|
|
90
|
+
if (!(def.get() instanceof Statements.MethodDef)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const name = def.findDirectExpression(Expressions.MethodName);
|
|
94
|
+
if (name === undefined) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const abapdoc = [];
|
|
98
|
+
let nextRow = undefined;
|
|
99
|
+
for (let j = i - 1; j >= 0 && statements[j].get() instanceof _statement_1.Comment; j--) {
|
|
100
|
+
const comment = statements[j];
|
|
101
|
+
const start = comment.getStart();
|
|
102
|
+
if (start.isAfter(name.getFirstToken().getStart())) {
|
|
103
|
+
continue; // comment inside the statement, after the method name
|
|
104
|
+
}
|
|
105
|
+
nextRow = nextRow !== null && nextRow !== void 0 ? nextRow : (_a = def.getTokens().find(t => t.getStart().isAfter(start))) === null || _a === void 0 ? void 0 : _a.getStart().getRow();
|
|
106
|
+
if (this.isAbapdoc(comment, def) === false || start.getRow() + 1 !== nextRow) {
|
|
107
|
+
break; // a blank line or a plain comment ends the block
|
|
108
|
+
}
|
|
109
|
+
abapdoc.unshift(comment);
|
|
110
|
+
nextRow = start.getRow();
|
|
111
|
+
}
|
|
112
|
+
if (abapdoc.length === 0) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const parameters = def.findAllExpressions(Expressions.MethodParamName)
|
|
116
|
+
.map(p => p.getFirstToken().getStr().replace(/^!/, "").toUpperCase());
|
|
117
|
+
const documented = new Set();
|
|
118
|
+
for (const comment of abapdoc) {
|
|
119
|
+
const match = comment.getFirstToken().getStr().match(/^"!\s*@parameter\s+!?([^\s|]+)/i);
|
|
120
|
+
if (match === null) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const parameter = match[1].toUpperCase();
|
|
124
|
+
let message = undefined;
|
|
125
|
+
if (parameters.includes(parameter) === false) {
|
|
126
|
+
message = `ABAP Doc parameter "${match[1]}" is not a parameter of method ${name.concatTokens()}`;
|
|
127
|
+
}
|
|
128
|
+
else if (documented.has(parameter) === true) {
|
|
129
|
+
message = `ABAP Doc parameter "${match[1]}" documented more than once`;
|
|
130
|
+
}
|
|
131
|
+
documented.add(parameter);
|
|
132
|
+
if (message !== undefined) {
|
|
133
|
+
issues.push(issue_1.Issue.atStatement(file, comment, message, this.getMetadata().key, this.conf.severity));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return issues;
|
|
138
|
+
}
|
|
139
|
+
/** ABAP Doc in front of the method name, after the colon of a chain */
|
|
140
|
+
isAbapdoc(comment, def) {
|
|
141
|
+
if (comment.getFirstToken().getStr().startsWith(`"!`) === false) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const colon = def.getColon();
|
|
145
|
+
return colon === undefined || comment.getStart().isAfter(colon.getStart());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.AbapdocParameterNames = AbapdocParameterNames;
|
|
149
|
+
//# sourceMappingURL=abapdoc_parameter_names.js.map
|
|
@@ -288,10 +288,10 @@ Missing objects are only reported when their names match the configured errorNam
|
|
|
288
288
|
let searchable;
|
|
289
289
|
for (const annotation of tree.findAllExpressionsRecursive(expressions_1.CDSAnnotation)) {
|
|
290
290
|
const normalized = annotation.concatTokens().replace(/\s/g, "").toUpperCase().replace(/^@</, "@");
|
|
291
|
-
if (this.hasSearchAnnotation(normalized,
|
|
291
|
+
if (this.hasSearchAnnotation(normalized, /[.{,]DEFAULTSEARCHELEMENT:TRUE/)) {
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
|
-
else if (searchable === undefined && this.hasSearchAnnotation(normalized,
|
|
294
|
+
else if (searchable === undefined && this.hasSearchAnnotation(normalized, /[.{,]SEARCHABLE:TRUE/)) {
|
|
295
295
|
searchable = annotation;
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -301,11 +301,11 @@ Missing objects are only reported when their names match the configured errorNam
|
|
|
301
301
|
issues.push(issue_1.Issue.atToken(file, searchable.getFirstToken(), message, this.getMetadata().key, this.conf.severity));
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
-
hasSearchAnnotation(normalized,
|
|
304
|
+
hasSearchAnnotation(normalized, annotation) {
|
|
305
305
|
if (normalized.startsWith("@SEARCH") === false) {
|
|
306
306
|
return false;
|
|
307
307
|
}
|
|
308
|
-
return
|
|
308
|
+
return annotation.test(normalized);
|
|
309
309
|
}
|
|
310
310
|
checkReservedNames(tree, issues, file) {
|
|
311
311
|
var _a, _b;
|
|
@@ -55,6 +55,7 @@ class ChangeIfToCase extends _abap_rule_1.ABAPRule {
|
|
|
55
55
|
constructor() {
|
|
56
56
|
super(...arguments);
|
|
57
57
|
this.conf = new ChangeIfToCaseConf();
|
|
58
|
+
this.skipNames = [];
|
|
58
59
|
}
|
|
59
60
|
getMetadata() {
|
|
60
61
|
return {
|
|
@@ -81,6 +82,7 @@ ENDCASE.`,
|
|
|
81
82
|
}
|
|
82
83
|
setConfig(conf) {
|
|
83
84
|
this.conf = conf;
|
|
85
|
+
this.skipNames = (this.conf.skipNames || []).map(skip => new RegExp(skip, "i"));
|
|
84
86
|
}
|
|
85
87
|
runParsed(file) {
|
|
86
88
|
var _a;
|
|
@@ -158,8 +160,7 @@ ENDCASE.`,
|
|
|
158
160
|
else {
|
|
159
161
|
return false;
|
|
160
162
|
}
|
|
161
|
-
for (const
|
|
162
|
-
const reg = new RegExp(skip, "i");
|
|
163
|
+
for (const reg of this.skipNames) {
|
|
163
164
|
if (chain.match(reg)) {
|
|
164
165
|
return false;
|
|
165
166
|
}
|