@abaplint/core 2.120.78 → 2.120.79

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.
@@ -23,6 +23,7 @@ export declare abstract class ABAPObject extends AbstractObject {
23
23
  } | undefined;
24
24
  private textsTranslations;
25
25
  private rawXMLCache;
26
+ private parsing;
26
27
  syntaxResult: ISyntaxResult | undefined;
27
28
  abstract getSequencedFiles(): readonly ABAPFile[];
28
29
  abstract getDescription(): string | undefined;
@@ -7808,6 +7809,7 @@ declare type SyntaxInput = {
7808
7809
 
7809
7810
  export declare class SyntaxLogic {
7810
7811
  private currentFile;
7812
+ private readonly includeStack;
7811
7813
  private issues;
7812
7814
  private deferred;
7813
7815
  private readonly object;
@@ -92,7 +92,7 @@ class ExpandMacros {
92
92
  listMacroNames() {
93
93
  return this.macros.listMacroNames();
94
94
  }
95
- find(statements, file, clear = true) {
95
+ find(statements, file, clear = true, includePath = new Set()) {
96
96
  var _a, _b, _c;
97
97
  let nameToken = undefined;
98
98
  let start = undefined;
@@ -113,14 +113,16 @@ class ExpandMacros {
113
113
  else if (type instanceof Statements.Include) {
114
114
  const includeName = (_b = statement.findDirectExpression(Expressions.IncludeName)) === null || _b === void 0 ? void 0 : _b.concatTokens();
115
115
  // todo, this does not take function module includes into account
116
- // todo, workaround for cyclic includes?
117
116
  const prog = (_c = this.reg) === null || _c === void 0 ? void 0 : _c.getObject("PROG", includeName);
118
117
  if (prog) {
119
118
  prog.parse(this.release, this.globalMacros, this.reg, this.languageVersion);
120
119
  const includeMainFile = prog.getMainABAPFile();
121
- if (includeMainFile) {
120
+ // an include that is already on the current include path is not walked again
121
+ if (includeMainFile && includePath.has(includeMainFile.getFilename()) === false) {
122
+ includePath.add(includeMainFile.getFilename());
122
123
  // slow, this copies everything,
123
- this.find([...includeMainFile.getStatements()], includeMainFile, false);
124
+ this.find([...includeMainFile.getStatements()], includeMainFile, false, includePath);
125
+ includePath.delete(includeMainFile.getFilename());
124
126
  }
125
127
  }
126
128
  }
@@ -16,11 +16,11 @@ class Convert {
16
16
  const dat = (0, combi_1.seq)("DATE", expressions_1.Source);
17
17
  const tim = (0, combi_1.seq)("TIME", expressions_1.Source);
18
18
  const stamp = (0, combi_1.seq)("INTO TIME STAMP", expressions_1.Target);
19
- const intoutc = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("INTO UTCLONG", expressions_1.Target));
19
+ const intoutc = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("INTO UTCLONG", expressions_1.Target), { also: combi_1.AlsoIn.OpenABAP });
20
20
  const invert = (0, combi_1.seq)("INTO INVERTED-DATE", expressions_1.Target);
21
21
  const date = (0, combi_1.seq)((0, combi_1.per)(dat, tim), (0, combi_1.per)(daylightSource, stamp, zone, invert, intoutc));
22
22
  const inv = (0, combi_1.seq)("INVERTED-DATE", expressions_1.Source, "INTO DATE", expressions_1.Target);
23
- const utclong = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("UTCLONG", expressions_1.Source, (0, combi_1.per)(zone, into, daylightSource)), { also: combi_1.AlsoIn.OpenABAP });
23
+ const utclong = (0, combi_1.ver)(version_1.Release.v754, (0, combi_1.seq)("UTCLONG", expressions_1.Source, (0, combi_1.per)(zone, into, daylightTarget)), { also: combi_1.AlsoIn.OpenABAP });
24
24
  return (0, combi_1.seq)("CONVERT", (0, combi_1.alt)(time, date, inv, utclong));
25
25
  }
26
26
  }
@@ -1194,9 +1194,9 @@ BuiltIn.methods = {
1194
1194
  },
1195
1195
  optional: {
1196
1196
  "days": basic_1.IntegerType.get(),
1197
- "hour": basic_1.IntegerType.get(),
1198
- "minutes": basic_1.IntegerType.get(),
1199
- "seconds": new basic_1.FloatType(),
1197
+ "hours": basic_1.IntegerType.get(),
1198
+ "minutes": new basic_1.Integer8Type(),
1199
+ "seconds": new basic_1.DecFloat34Type(),
1200
1200
  },
1201
1201
  return: new basic_1.UTCLongType(),
1202
1202
  release: version_1.Release.v754,
@@ -1212,7 +1212,7 @@ BuiltIn.methods = {
1212
1212
  "high": new basic_1.UTCLongType(),
1213
1213
  "low": new basic_1.UTCLongType(),
1214
1214
  },
1215
- return: new basic_1.FloatType(),
1215
+ return: new basic_1.DecFloat34Type(),
1216
1216
  release: version_1.Release.v754,
1217
1217
  },
1218
1218
  "XSDBOOL": {
@@ -52,7 +52,12 @@ class Procedural {
52
52
  this.scope = scope;
53
53
  this.reg = reg;
54
54
  }
55
- addAllFormDefinitions(file, obj) {
55
+ addAllFormDefinitions(file, obj, visited = new Set()) {
56
+ // an include reached again through a cycle of INCLUDEs is not walked twice
57
+ if (visited.has(file.getFilename())) {
58
+ return;
59
+ }
60
+ visited.add(file.getFilename());
56
61
  const structure = file.getStructure();
57
62
  if (structure) {
58
63
  const input = {
@@ -73,12 +78,12 @@ class Procedural {
73
78
  for (const node of includes) {
74
79
  const found = this.findInclude(node, obj);
75
80
  if (found) {
76
- this.addAllFormDefinitions(found, obj);
81
+ this.addAllFormDefinitions(found, obj, visited);
77
82
  }
78
83
  }
79
84
  }
80
85
  findInclude(node, obj) {
81
- // assumption: no cyclic includes, includes not found are reported by rule "check_include"
86
+ // cyclic includes are cut by the callers, includes not found are reported by rule "check_include"
82
87
  // todo: how to make sure code is not duplicated here and in rule "check_include" / include graph?
83
88
  const expr = node.findFirstExpression(Expressions.IncludeName);
84
89
  if (expr === undefined) {
@@ -42,7 +42,11 @@ const _syntax_input_1 = require("../_syntax_input");
42
42
  // On a character or byte field of fixed length the substring must lie inside the field, else it is
43
43
  // a syntax error. Other types, a dynamic offset or length and "*" (undefined) are left alone, as is
44
44
  // length 0. The offset is applied first, so the length is compared to what remains after the offset.
45
+ // A substring of a date, time or numeric text field has type n; its bounds are not checked.
45
46
  function substringType(context, offset, length, node, input) {
47
+ if (context instanceof basic_1.DateType || context instanceof basic_1.TimeType || context instanceof basic_1.NumericType) {
48
+ return numericSubstringType(context, offset, length);
49
+ }
46
50
  if (!(context instanceof basic_1.CharacterType) && !(context instanceof basic_1.HexType)) {
47
51
  return context;
48
52
  }
@@ -65,6 +69,24 @@ function substringType(context, offset, length, node, input) {
65
69
  }
66
70
  return context;
67
71
  }
72
+ function numericSubstringType(context, offset, length) {
73
+ const hasOffset = offset !== undefined && offset > 0;
74
+ const hasLength = length !== undefined && length > 0;
75
+ if (hasOffset === false && hasLength === false) {
76
+ return context;
77
+ }
78
+ let remaining = context instanceof basic_1.DateType ? 8 : context instanceof basic_1.TimeType ? 6 : context.getLength();
79
+ if (hasOffset) {
80
+ remaining = remaining - offset;
81
+ }
82
+ if (hasLength) {
83
+ remaining = Math.min(remaining, length);
84
+ }
85
+ if (remaining <= 0) {
86
+ return context;
87
+ }
88
+ return new basic_1.NumericType(remaining);
89
+ }
68
90
  // A field symbol typed "c" or "x" is generic, but its type is represented with length 1, so the
69
91
  // offset or length at "index" is not checked when it directly follows the field symbol
70
92
  function isFieldSymbolItself(children, index) {
@@ -82,6 +82,16 @@ class Convert {
82
82
  target_1.Target.runSyntax(stampTarget, input);
83
83
  }
84
84
  }
85
+ const utclongTarget = node.findExpressionAfterToken("UTCLONG");
86
+ if ((utclongTarget === null || utclongTarget === void 0 ? void 0 : utclongTarget.get()) instanceof Expressions.Target) {
87
+ const inline = utclongTarget === null || utclongTarget === void 0 ? void 0 : utclongTarget.findDirectExpression(Expressions.InlineData);
88
+ if (inline) {
89
+ inline_data_1.InlineData.runSyntax(inline, input, new basic_1.UTCLongType());
90
+ }
91
+ else {
92
+ target_1.Target.runSyntax(utclongTarget, input);
93
+ }
94
+ }
85
95
  }
86
96
  }
87
97
  exports.Convert = Convert;
@@ -337,6 +337,8 @@ if (Object.keys(map).length === 0) {
337
337
  // -----------------------------------
338
338
  class SyntaxLogic {
339
339
  constructor(reg, object) {
340
+ // files the traversal walked into through INCLUDEs, outermost first
341
+ this.includeStack = [];
340
342
  this.reg = reg;
341
343
  this.issues = [];
342
344
  this.deferred = [];
@@ -444,11 +446,20 @@ class SyntaxLogic {
444
446
  // walk into INCLUDEs
445
447
  if (isStatement && child.get() instanceof Statements.Include) {
446
448
  const file = this.helpers.proc.findInclude(child, this.object);
447
- if (file !== undefined && file.getStructure() !== undefined) {
449
+ // an INCLUDE that leads back to a file already being walked is not followed again
450
+ const cyclic = file !== undefined && (file.getFilename() === this.currentFile.getFilename()
451
+ || this.includeStack.includes(file.getFilename()));
452
+ if (file !== undefined && file.getStructure() !== undefined && cyclic === false) {
448
453
  const old = this.currentFile;
454
+ this.includeStack.push(old.getFilename());
449
455
  this.currentFile = file;
450
- this.traverse(file.getStructure());
451
- this.currentFile = old;
456
+ try {
457
+ this.traverse(file.getStructure());
458
+ }
459
+ finally {
460
+ this.currentFile = old;
461
+ this.includeStack.pop();
462
+ }
452
463
  }
453
464
  }
454
465
  if (isStructure || isStatement) {
@@ -1,9 +1,44 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.CrossIncludeMacros = void 0;
4
37
  const _abap_object_1 = require("../objects/_abap_object");
5
38
  const program_1 = require("../objects/program");
6
39
  const _statement_1 = require("./2_statements/statements/_statement");
40
+ const statements_1 = require("./2_statements/statements");
41
+ const Expressions = __importStar(require("./2_statements/expressions"));
7
42
  const expand_macros_1 = require("./2_statements/expand_macros");
8
43
  const include_graph_1 = require("../utils/include_graph");
9
44
  // Macros can be defined in one include and used in a sibling include, connected
@@ -16,6 +51,7 @@ class CrossIncludeMacros {
16
51
  this.reg = reg;
17
52
  }
18
53
  run() {
54
+ this.reparseCyclicPrograms();
19
55
  const candidates = this.findCandidates();
20
56
  if (candidates.length === 0) {
21
57
  return;
@@ -43,6 +79,45 @@ class CrossIncludeMacros {
43
79
  }
44
80
  }
45
81
  //////////////////////////////
82
+ // A program or include in an INCLUDE cycle can be parsed while the include defining its macros is still
83
+ // being parsed, depending on the order the files were added. Everything is parsed now, so parse it again.
84
+ reparseCyclicPrograms() {
85
+ const config = this.reg.getConfig();
86
+ for (const obj of this.reg.getObjects()) {
87
+ if (obj instanceof program_1.Program && this.hasUnknown(obj) && this.isInIncludeCycle(obj)) {
88
+ obj.setDirty();
89
+ obj.parse(config.getRelease(), config.getSyntaxSetttings().globalMacros, this.reg, config.getLanguageVersion());
90
+ }
91
+ }
92
+ }
93
+ hasUnknown(prog) {
94
+ return prog.getABAPFiles().some(f => f.getStatements().some(s => s.get() instanceof _statement_1.Unknown));
95
+ }
96
+ // the include graph does not link INCLUDEs of main programs, so follow the INCLUDEs from the program
97
+ isInIncludeCycle(prog) {
98
+ var _a;
99
+ const seen = new Set();
100
+ const stack = [prog];
101
+ while (stack.length > 0) {
102
+ for (const file of stack.pop().getABAPFiles()) {
103
+ for (const statement of file.getStatements()) {
104
+ if (!(statement.get() instanceof statements_1.Include)) {
105
+ continue;
106
+ }
107
+ const name = (_a = statement.findDirectExpression(Expressions.IncludeName)) === null || _a === void 0 ? void 0 : _a.concatTokens();
108
+ const included = this.reg.getObject("PROG", name);
109
+ if (included === prog) {
110
+ return true;
111
+ }
112
+ else if (included instanceof program_1.Program && seen.has(included) === false) {
113
+ seen.add(included);
114
+ stack.push(included);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ return false;
120
+ }
46
121
  findCandidates() {
47
122
  const ret = [];
48
123
  for (const obj of this.reg.getObjects()) {
@@ -11,6 +11,7 @@ class ABAPObject extends _abstract_object_1.AbstractObject {
11
11
  }
12
12
  constructor(name) {
13
13
  super(name);
14
+ this.parsing = false;
14
15
  this.parsed = [];
15
16
  this.texts = undefined;
16
17
  this.rawXMLCache = undefined;
@@ -19,11 +20,19 @@ class ABAPObject extends _abstract_object_1.AbstractObject {
19
20
  return !!x && x instanceof ABAPObject;
20
21
  }
21
22
  parse(release, globalMacros, reg, languageVersion = version_1.LanguageVersion.Normal) {
22
- if (this.isDirty() === false) {
23
+ // an INCLUDE that leads back to this object while it is being parsed is not followed again
24
+ if (this.isDirty() === false || this.parsing === true) {
23
25
  return { updated: false, runtime: 0 };
24
26
  }
25
27
  const abapFiles = this.getFiles().filter(f => f.getFilename().endsWith(".abap"));
26
- const result = new abap_parser_1.ABAPParser({ release, globalMacros, reg, languageVersion }).parse(abapFiles);
28
+ let result;
29
+ this.parsing = true;
30
+ try {
31
+ result = new abap_parser_1.ABAPParser({ release, globalMacros, reg, languageVersion }).parse(abapFiles);
32
+ }
33
+ finally {
34
+ this.parsing = false;
35
+ }
27
36
  this.parsed = result.output;
28
37
  this.old = result.issues;
29
38
  this.dirty = false;
@@ -75,7 +75,7 @@ class Registry {
75
75
  }
76
76
  static abaplintVersion() {
77
77
  // magic, see build script "version.js"
78
- return "2.120.78";
78
+ return "2.120.79";
79
79
  }
80
80
  getDDICReferences() {
81
81
  return this.ddicReferences;
@@ -40,6 +40,7 @@ const issue_1 = require("../issue");
40
40
  const _abap_rule_1 = require("./_abap_rule");
41
41
  const _basic_rule_config_1 = require("./_basic_rule_config");
42
42
  const _irule_1 = require("./_irule");
43
+ const edit_helper_1 = require("../edit_helper");
43
44
  class MaxOneMethodParameterPerLineConf extends _basic_rule_config_1.BasicRuleConfig {
44
45
  }
45
46
  exports.MaxOneMethodParameterPerLineConf = MaxOneMethodParameterPerLineConf;
@@ -53,7 +54,7 @@ class MaxOneMethodParameterPerLine extends _abap_rule_1.ABAPRule {
53
54
  key: "max_one_method_parameter_per_line",
54
55
  title: "Max one method parameter definition per line",
55
56
  shortDescription: `Keep max one method parameter description per line`,
56
- tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Whitespace],
57
+ tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Whitespace, _irule_1.RuleTag.Quickfix],
57
58
  badExample: `
58
59
  METHODS apps_scope_token
59
60
  IMPORTING
@@ -82,7 +83,8 @@ METHODS apps_scope_token
82
83
  continue;
83
84
  }
84
85
  if (prev.getFirstToken().getStart().getRow() === p.getFirstToken().getStart().getRow()) {
85
- const issue = issue_1.Issue.atToken(file, prev.getFirstToken(), this.getMetadata().title, this.getMetadata().key, this.conf.severity);
86
+ const fix = this.buildFix(file, statement, p);
87
+ const issue = issue_1.Issue.atToken(file, prev.getFirstToken(), this.getMetadata().title, this.getMetadata().key, this.conf.severity, fix);
86
88
  issues.push(issue);
87
89
  }
88
90
  prev = p;
@@ -90,6 +92,30 @@ METHODS apps_scope_token
90
92
  }
91
93
  return issues;
92
94
  }
95
+ /** moves the parameter, or its preceding section keyword, to a new line */
96
+ buildFix(file, statement, param) {
97
+ const tokens = statement.getTokens();
98
+ const index = tokens.indexOf(param.getFirstToken());
99
+ if (index < 1) {
100
+ return undefined;
101
+ }
102
+ let target = tokens[index];
103
+ let indent = statement.findFirstExpression(Expressions.MethodParam).getFirstToken().getStart().getCol();
104
+ const before = tokens[index - 1];
105
+ if (MaxOneMethodParameterPerLine.SECTIONS.includes(before.getStr().toUpperCase())) {
106
+ if (index < 2) {
107
+ return undefined;
108
+ }
109
+ target = before;
110
+ indent = tokens.find(t => MaxOneMethodParameterPerLine.SECTIONS.includes(t.getStr().toUpperCase())).getStart().getCol();
111
+ }
112
+ const previous = tokens[tokens.indexOf(target) - 1];
113
+ if (previous.getEnd().getRow() !== target.getStart().getRow()) {
114
+ return undefined;
115
+ }
116
+ return edit_helper_1.EditHelper.replaceRange(file, previous.getEnd(), target.getStart(), "\n" + " ".repeat(indent - 1));
117
+ }
93
118
  }
94
119
  exports.MaxOneMethodParameterPerLine = MaxOneMethodParameterPerLine;
120
+ MaxOneMethodParameterPerLine.SECTIONS = ["IMPORTING", "EXPORTING", "CHANGING", "RETURNING"];
95
121
  //# sourceMappingURL=max_one_method_parameter_per_line.js.map
@@ -39,7 +39,7 @@ class PreferPragmas extends _abap_rule_1.ABAPRule {
39
39
  return {
40
40
  key: "prefer_pragmas",
41
41
  title: "prefer pragmas over pseudo comments ",
42
- shortDescription: `prefer pragmas over pseudo comments `,
42
+ shortDescription: `prefer pragmas over pseudo comments`,
43
43
  extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-pragmas-to-pseudo-comments`,
44
44
  tags: [_irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Styleguide],
45
45
  badExample: `DATA foo1 TYPE i. "#EC NEEDED`,
@@ -196,9 +196,9 @@ text = text.`,
196
196
  context = this.infer(context, this.sourceType(child, scope));
197
197
  }
198
198
  else if (child.get() instanceof Expressions.ArithOperator) {
199
- if (child.concatTokens() === "**") {
200
- context = new basic_1.FloatType();
201
- }
199
+ // the CONV sets the calculation type of arithmetic expressions, only redundant
200
+ // if the assignment target has the same type, see arithmeticUsesSameTargetType
201
+ return undefined;
202
202
  }
203
203
  else {
204
204
  return undefined;
@@ -9,7 +9,6 @@ const position_1 = require("../position");
9
9
  const issue_1 = require("../issue");
10
10
  const _abap_object_1 = require("../objects/_abap_object");
11
11
  const severity_1 = require("../severity");
12
- // todo, check for cycles/circular dependencies, method findTop
13
12
  // todo, add configurable error for multiple use includes
14
13
  const FMXXINCLUDE = /^(\/\w+\/)?L.+XX$/;
15
14
  function getABAPObjects(reg) {
@@ -45,11 +44,33 @@ class Graph {
45
44
  }
46
45
  this.edges[from.filename].push(toFilename);
47
46
  }
48
- findTop(filename) {
47
+ // true if "to" is reachable from "from" via the edges, "from" itself counts
48
+ isReachable(from, to) {
49
+ const seen = new Set();
50
+ const stack = [from];
51
+ while (stack.length > 0) {
52
+ const current = stack.pop();
53
+ if (current === to) {
54
+ return true;
55
+ }
56
+ if (seen.has(current) === false) {
57
+ seen.add(current);
58
+ stack.push(...this.edges[current] || []);
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+ findTop(filename, path = new Set()) {
49
64
  const ret = [];
65
+ path.add(filename);
50
66
  for (const to of this.edges[filename] || []) {
51
- ret.push(...this.findTop(to));
67
+ // an INCLUDE cycle, the file is already on the current path
68
+ if (path.has(to)) {
69
+ continue;
70
+ }
71
+ ret.push(...this.findTop(to, path));
52
72
  }
73
+ path.delete(filename);
53
74
  if (ret.length === 0) {
54
75
  const found = this.findVertexByFilename(filename);
55
76
  if (found !== undefined) {
@@ -104,6 +125,7 @@ class IncludeGraph {
104
125
  ///////////////////////////////
105
126
  build() {
106
127
  this.addVertices();
128
+ const included = [];
107
129
  for (const o of getABAPObjects(this.reg)) {
108
130
  for (const f of o.getABAPFiles()) {
109
131
  if (f.getFilename().includes(".prog.screen_") || f.getFilename().includes(".fugr.screen_")) {
@@ -134,11 +156,19 @@ class IncludeGraph {
134
156
  }
135
157
  else {
136
158
  this.graph.addEdge(found, f.getFilename());
159
+ included.push({ file: f, statement: s, vertex: found, name });
137
160
  }
138
161
  }
139
162
  }
140
163
  }
141
164
  }
165
+ // cycles are not followed, but ABAP rejects them, so report them
166
+ for (const i of included) {
167
+ if (this.graph.isReachable(i.file.getFilename(), i.vertex.filename)) {
168
+ const issue = issue_1.Issue.atStatement(i.file, i.statement, "Cyclic INCLUDE, " + i.name, new check_include_1.CheckInclude().getMetadata().key, this.severity);
169
+ this.issues.push(issue);
170
+ }
171
+ }
142
172
  }
143
173
  addVertices() {
144
174
  for (const o of getABAPObjects(this.reg)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/core",
3
- "version": "2.120.78",
3
+ "version": "2.120.79",
4
4
  "description": "abaplint - Core API",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/abaplint.d.ts",