@abaplint/core 2.120.56 → 2.120.58

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.
@@ -39,7 +39,8 @@ const Expressions = __importStar(require("."));
39
39
  const tokens_1 = require("../../1_lexer/tokens");
40
40
  class MethodDefReturning extends combi_1.Expression {
41
41
  getRunnable() {
42
- const value = (0, combi_1.seq)("VALUE", (0, combi_1.tok)(tokens_1.ParenLeft), Expressions.MethodParamName, (0, combi_1.tok)(tokens_1.ParenRightW));
42
+ // see MethodParam: the lexer gives "!VALUE" as one Identifier
43
+ const value = (0, combi_1.seq)((0, combi_1.altPrio)("VALUE", "!VALUE"), (0, combi_1.tok)(tokens_1.ParenLeft), Expressions.MethodParamName, (0, combi_1.tok)(tokens_1.ParenRightW));
43
44
  return (0, combi_1.seq)("RETURNING", value, Expressions.TypeParam);
44
45
  }
45
46
  }
@@ -39,8 +39,11 @@ const Expressions = __importStar(require("."));
39
39
  const tokens_1 = require("../../1_lexer/tokens");
40
40
  class MethodParam extends combi_1.Expression {
41
41
  getRunnable() {
42
- const ref = (0, combi_1.seq)("REFERENCE", (0, combi_1.tok)(tokens_1.ParenLeft), Expressions.MethodParamName, (0, combi_1.tok)(tokens_1.ParenRightW));
43
- const value = (0, combi_1.seq)("VALUE", (0, combi_1.tok)(tokens_1.ParenLeft), Expressions.MethodParamName, (0, combi_1.tok)(tokens_1.ParenRightW));
42
+ // The escape is part of the token: the lexer gives "!VALUE" as a single
43
+ // Identifier, so a literal "VALUE" cannot match it and the whole METHODS
44
+ // statement falls back to Unknown.
45
+ const ref = (0, combi_1.seq)((0, combi_1.altPrio)("REFERENCE", "!REFERENCE"), (0, combi_1.tok)(tokens_1.ParenLeft), Expressions.MethodParamName, (0, combi_1.tok)(tokens_1.ParenRightW));
46
+ const value = (0, combi_1.seq)((0, combi_1.altPrio)("VALUE", "!VALUE"), (0, combi_1.tok)(tokens_1.ParenLeft), Expressions.MethodParamName, (0, combi_1.tok)(tokens_1.ParenRightW));
44
47
  const fieldsOrValue = (0, combi_1.seq)((0, combi_1.altPrio)(value, ref, Expressions.MethodParamName), Expressions.TypeParam);
45
48
  return fieldsOrValue;
46
49
  }
@@ -305,6 +305,10 @@ class StatementParser {
305
305
  let add = [];
306
306
  let pre = [];
307
307
  let colon = undefined;
308
+ // inside EXEC SQL and an AMDP body a colon reads a host variable, ":lv_a",
309
+ // and is not the chaining marker; recognized here because nativeSQL() runs
310
+ // afterwards and can only re-wrap what this has already cut
311
+ let native = false;
308
312
  for (const token of wa.tokens) {
309
313
  if (token instanceof Tokens.Comment) {
310
314
  wa.statements.push(new nodes_1.StatementNode(new _statement_1.Comment()).setChildren(this.tokensToNodes([token])));
@@ -314,6 +318,7 @@ class StatementParser {
314
318
  const str = token.getStr();
315
319
  if (str.length === 1) {
316
320
  if (str === ".") {
321
+ native = this.nativeAfter(native, add, colon);
317
322
  wa.addUnknown(pre, add, colon);
318
323
  add = [];
319
324
  pre = [];
@@ -323,13 +328,13 @@ class StatementParser {
323
328
  wa.addUnknown(pre, add, colon);
324
329
  add = [];
325
330
  }
326
- else if (str === ":" && colon === undefined) {
331
+ else if (str === ":" && colon === undefined && native === false) {
327
332
  colon = token;
328
333
  add.pop(); // do not add colon token to statement
329
334
  pre.push(...add);
330
335
  add = [];
331
336
  }
332
- else if (str === ":") {
337
+ else if (str === ":" && native === false) {
333
338
  add.pop(); // do not add colon token to statement
334
339
  }
335
340
  }
@@ -338,6 +343,36 @@ class StatementParser {
338
343
  wa.addUnknown(pre, add, colon);
339
344
  }
340
345
  }
346
+ /** whether the statement that just ended opens or closes a region where a
347
+ * colon belongs to the statement rather than chaining it */
348
+ nativeAfter(native, tokens, colon) {
349
+ // a pragma sits between the closing keyword and the period, so it is left
350
+ // out before the last word is read: "ENDEXEC ##NEEDED." closes the region
351
+ const words = tokens.filter(t => !(t instanceof Tokens.Pragma)).map(t => t.getStr().toUpperCase());
352
+ // a native body usually arrives as one blob terminated by its own closing
353
+ // keyword, so the terminator is the word before the period
354
+ if (words[words.length - 2] === "ENDEXEC" || words[words.length - 2] === "ENDMETHOD") {
355
+ return false;
356
+ }
357
+ if (colon !== undefined) {
358
+ // a fragment of a chain cannot OPEN a region: it is only part of a
359
+ // statement, and taking "DATA: lv_a TYPE i, exec sql." for one would
360
+ // turn chaining off for the rest of the file
361
+ return native;
362
+ }
363
+ if (words[0] === "EXEC" && words[1] === "SQL") {
364
+ return true;
365
+ }
366
+ // "BY DATABASE" as well as LANGUAGE, and not LANGUAGE alone: a method
367
+ // may be NAMED language
368
+ if (words[0] === "METHOD"
369
+ && words.includes("BY")
370
+ && words.includes("DATABASE")
371
+ && words.includes("LANGUAGE")) {
372
+ return true;
373
+ }
374
+ return native;
375
+ }
341
376
  }
342
377
  exports.StatementParser = StatementParser;
343
378
  //# sourceMappingURL=statement_parser.js.map
@@ -333,7 +333,9 @@ class MethodParameters {
333
333
  }
334
334
  isPassByValue(param) {
335
335
  var _a;
336
- return param.getFirstToken().getStr().toUpperCase() === "VALUE"
336
+ // "!" is the identifier escape, generated code has eg. "IMPORTING !VALUE(iv_foo)"
337
+ const first = param.getFirstToken().getStr().toUpperCase();
338
+ return (first === "VALUE" || first === "!VALUE")
337
339
  && ((_a = param.getChildren()[1]) === null || _a === void 0 ? void 0 : _a.getFirstToken().getStr()) === "(";
338
340
  }
339
341
  }
@@ -75,7 +75,7 @@ class Registry {
75
75
  }
76
76
  static abaplintVersion() {
77
77
  // magic, see build script "version.js"
78
- return "2.120.56";
78
+ return "2.120.58";
79
79
  }
80
80
  getDDICReferences() {
81
81
  return this.ddicReferences;
@@ -149,7 +149,9 @@ __exportStar(require("./prefer_insert_into_table"), exports);
149
149
  __exportStar(require("./prefer_is_not"), exports);
150
150
  __exportStar(require("./prefer_pragmas"), exports);
151
151
  __exportStar(require("./prefer_abap_bool"), exports);
152
+ __exportStar(require("./prefer_abap_bool_values"), exports);
152
153
  __exportStar(require("./prefer_raise_exception_new"), exports);
154
+ __exportStar(require("./prefer_string_template"), exports);
153
155
  __exportStar(require("./prefer_returning_to_exporting"), exports);
154
156
  __exportStar(require("./prefer_xsdbool"), exports);
155
157
  __exportStar(require("./preferred_compare_operator"), exports);
@@ -0,0 +1,256 @@
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.PreferAbapBoolValues = exports.PreferAbapBoolValuesConf = void 0;
37
+ const issue_1 = require("../issue");
38
+ const _abap_rule_1 = require("./_abap_rule");
39
+ const Expressions = __importStar(require("../abap/2_statements/expressions"));
40
+ const Statements = __importStar(require("../abap/2_statements/statements"));
41
+ const _basic_rule_config_1 = require("./_basic_rule_config");
42
+ const _irule_1 = require("./_irule");
43
+ const edit_helper_1 = require("../edit_helper");
44
+ const nodes_1 = require("../abap/nodes");
45
+ const ABAP_BOOL = "ABAP_BOOL";
46
+ const ABAP_TRUE = "abap_true";
47
+ const ABAP_FALSE = "abap_false";
48
+ const VALID_VALUES = new Set(["ABAP_TRUE", "ABAP_FALSE", "ABAP_UNDEFINED"]);
49
+ class PreferAbapBoolValuesConf extends _basic_rule_config_1.BasicRuleConfig {
50
+ }
51
+ exports.PreferAbapBoolValuesConf = PreferAbapBoolValuesConf;
52
+ class PreferAbapBoolValues extends _abap_rule_1.ABAPRule {
53
+ constructor() {
54
+ super(...arguments);
55
+ this.conf = new PreferAbapBoolValuesConf();
56
+ }
57
+ getMetadata() {
58
+ return {
59
+ key: "prefer_abap_bool_values",
60
+ title: "Prefer abap_true and abap_false",
61
+ shortDescription: `Use abap_true and abap_false instead of 'X' and ' ' for abap_bool variables`,
62
+ // eslint-disable-next-line max-len
63
+ extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#use-abap_true-abap_false-and-abap_undefined-for-abap_bool-variables`,
64
+ tags: [_irule_1.RuleTag.Styleguide, _irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Quickfix],
65
+ badExample: `DATA lv_flag TYPE abap_bool.\nlv_flag = 'X'.`,
66
+ goodExample: `DATA lv_flag TYPE abap_bool.\nlv_flag = abap_true.`,
67
+ };
68
+ }
69
+ getConfig() {
70
+ return this.conf;
71
+ }
72
+ setConfig(conf) {
73
+ this.conf = conf;
74
+ }
75
+ runParsed(file) {
76
+ const issues = [];
77
+ const structure = file.getStructure();
78
+ if (structure === undefined) {
79
+ return [];
80
+ }
81
+ const boolVars = this.collectBoolVars(file);
82
+ // Check Move statements: target = source
83
+ for (const stat of structure.findAllStatements(Statements.Move)) {
84
+ const target = stat.findFirstExpression(Expressions.Target);
85
+ const source = stat.findFirstExpression(Expressions.Source);
86
+ if (target === undefined || source === undefined) {
87
+ continue;
88
+ }
89
+ const targetName = target.concatTokens().toUpperCase();
90
+ if (!boolVars.has(targetName)) {
91
+ continue;
92
+ }
93
+ this.checkSourceNode(source, file, issues);
94
+ }
95
+ // Check comparisons: IF/ELSEIF conditions
96
+ const compareStatements = [
97
+ ...structure.findAllStatements(Statements.If),
98
+ ...structure.findAllStatements(Statements.ElseIf),
99
+ ];
100
+ for (const stat of compareStatements) {
101
+ const cond = stat.findFirstExpression(Expressions.Cond);
102
+ if (cond === undefined) {
103
+ continue;
104
+ }
105
+ for (const compare of cond.findAllExpressions(Expressions.Compare)) {
106
+ this.checkCompareBoolLiteral(compare, boolVars, file, issues);
107
+ }
108
+ }
109
+ return issues;
110
+ }
111
+ collectBoolVars(file) {
112
+ var _a;
113
+ const boolVars = new Set();
114
+ const structure = file.getStructure();
115
+ if (structure === undefined) {
116
+ return boolVars;
117
+ }
118
+ const statements = [
119
+ ...structure.findAllStatements(Statements.Data),
120
+ ...structure.findAllStatements(Statements.ClassData),
121
+ ...structure.findAllStatements(Statements.FieldSymbol),
122
+ ];
123
+ for (const stat of statements) {
124
+ const typeNameExpr = stat.findFirstExpression(Expressions.TypeName);
125
+ if (typeNameExpr === undefined) {
126
+ continue;
127
+ }
128
+ if (typeNameExpr.concatTokens().toUpperCase() !== ABAP_BOOL) {
129
+ continue;
130
+ }
131
+ const nameExpr = (_a = stat.findFirstExpression(Expressions.DefinitionName)) !== null && _a !== void 0 ? _a : stat.findFirstExpression(Expressions.FieldSymbol);
132
+ if (nameExpr === undefined) {
133
+ continue;
134
+ }
135
+ boolVars.add(nameExpr.concatTokens().toUpperCase());
136
+ }
137
+ return boolVars;
138
+ }
139
+ checkSourceNode(source, file, issues) {
140
+ const firstToken = source.getFirstToken().getStr().toUpperCase();
141
+ if (firstToken === "COND") {
142
+ const condBody = source.findDirectExpression(Expressions.CondBody);
143
+ if (condBody) {
144
+ this.checkCondBodyBranches(condBody, file, issues);
145
+ }
146
+ return;
147
+ }
148
+ if (firstToken === "SWITCH") {
149
+ const switchBody = source.findDirectExpression(Expressions.SwitchBody);
150
+ if (switchBody) {
151
+ this.checkSwitchBodyBranches(switchBody, file, issues);
152
+ }
153
+ return;
154
+ }
155
+ // Plain literal assignment
156
+ const issue = this.checkLiteralSource(source, file);
157
+ if (issue) {
158
+ issues.push(issue);
159
+ }
160
+ }
161
+ checkLiteralSource(source, file) {
162
+ const constantStr = source.findFirstExpression(Expressions.ConstantString);
163
+ if (constantStr === undefined) {
164
+ return undefined;
165
+ }
166
+ const token = constantStr.getFirstToken();
167
+ const val = token.getStr();
168
+ if (val === "'X'") {
169
+ const fix = edit_helper_1.EditHelper.replaceToken(file, token, ABAP_TRUE);
170
+ return issue_1.Issue.atToken(file, token, `Use ${ABAP_TRUE} instead of 'X'`, this.getMetadata().key, this.conf.severity, fix);
171
+ }
172
+ if (val === "' '") {
173
+ const fix = edit_helper_1.EditHelper.replaceToken(file, token, ABAP_FALSE);
174
+ return issue_1.Issue.atToken(file, token, `Use ${ABAP_FALSE} instead of ' '`, this.getMetadata().key, this.conf.severity, fix);
175
+ }
176
+ return undefined;
177
+ }
178
+ checkCondBodyBranches(condBody, file, issues) {
179
+ // CondBody: WHEN <cond> THEN <source> [WHEN <cond> THEN <source>]* [ELSE <source>]
180
+ // Walk children; Source nodes after THEN/ELSE are the result values
181
+ let afterThenOrElse = false;
182
+ for (const child of condBody.getChildren()) {
183
+ const tokenStr = child.getFirstToken().getStr().toUpperCase();
184
+ if (tokenStr === "THEN" || tokenStr === "ELSE") {
185
+ afterThenOrElse = true;
186
+ continue;
187
+ }
188
+ if (tokenStr === "WHEN") {
189
+ afterThenOrElse = false;
190
+ continue;
191
+ }
192
+ if (afterThenOrElse && child instanceof nodes_1.ExpressionNode && child.get() instanceof Expressions.Source) {
193
+ const issue = this.checkLiteralSource(child, file);
194
+ if (issue) {
195
+ issues.push(issue);
196
+ }
197
+ afterThenOrElse = false;
198
+ }
199
+ }
200
+ }
201
+ checkSwitchBodyBranches(switchBody, file, issues) {
202
+ // SwitchBody: <source> WHEN <val> [OR <val>]* THEN <source> [ELSE <source>]
203
+ // First Source is the operand; Source after THEN/ELSE are result values
204
+ let firstSourceSeen = false;
205
+ let afterThenOrElse = false;
206
+ for (const child of switchBody.getChildren()) {
207
+ const tokenStr = child.getFirstToken().getStr().toUpperCase();
208
+ if (tokenStr === "THEN" || tokenStr === "ELSE") {
209
+ afterThenOrElse = true;
210
+ continue;
211
+ }
212
+ if (tokenStr === "WHEN" || tokenStr === "OR") {
213
+ afterThenOrElse = false;
214
+ continue;
215
+ }
216
+ if (child instanceof nodes_1.ExpressionNode && child.get() instanceof Expressions.Source) {
217
+ if (!firstSourceSeen) {
218
+ firstSourceSeen = true;
219
+ continue;
220
+ }
221
+ if (afterThenOrElse) {
222
+ const issue = this.checkLiteralSource(child, file);
223
+ if (issue) {
224
+ issues.push(issue);
225
+ }
226
+ afterThenOrElse = false;
227
+ }
228
+ }
229
+ }
230
+ }
231
+ checkCompareBoolLiteral(compare, boolVars, file, issues) {
232
+ // Compare: Source CompareOperator Source (or other forms)
233
+ const sources = compare.findDirectExpressions(Expressions.Source);
234
+ if (sources.length < 2) {
235
+ return;
236
+ }
237
+ const [left, right] = sources;
238
+ const leftStr = left.concatTokens().toUpperCase();
239
+ const rightStr = right.concatTokens().toUpperCase();
240
+ let literalSource;
241
+ if (boolVars.has(leftStr) && !VALID_VALUES.has(rightStr)) {
242
+ literalSource = right;
243
+ }
244
+ else if (boolVars.has(rightStr) && !VALID_VALUES.has(leftStr)) {
245
+ literalSource = left;
246
+ }
247
+ if (literalSource) {
248
+ const issue = this.checkLiteralSource(literalSource, file);
249
+ if (issue) {
250
+ issues.push(issue);
251
+ }
252
+ }
253
+ }
254
+ }
255
+ exports.PreferAbapBoolValues = PreferAbapBoolValues;
256
+ //# sourceMappingURL=prefer_abap_bool_values.js.map
@@ -0,0 +1,135 @@
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.PreferStringTemplate = exports.PreferStringTemplateConf = void 0;
37
+ const issue_1 = require("../issue");
38
+ const _abap_rule_1 = require("./_abap_rule");
39
+ const Expressions = __importStar(require("../abap/2_statements/expressions"));
40
+ const _basic_rule_config_1 = require("./_basic_rule_config");
41
+ const _irule_1 = require("./_irule");
42
+ const nodes_1 = require("../abap/nodes");
43
+ const edit_helper_1 = require("../edit_helper");
44
+ class PreferStringTemplateConf extends _basic_rule_config_1.BasicRuleConfig {
45
+ }
46
+ exports.PreferStringTemplateConf = PreferStringTemplateConf;
47
+ class PreferStringTemplate extends _abap_rule_1.ABAPRule {
48
+ constructor() {
49
+ super(...arguments);
50
+ this.conf = new PreferStringTemplateConf();
51
+ }
52
+ getMetadata() {
53
+ return {
54
+ key: "prefer_string_template",
55
+ title: "Prefer string template over &&",
56
+ shortDescription: `Use string templates instead of && to assemble text`,
57
+ extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#use--to-assemble-text`,
58
+ tags: [_irule_1.RuleTag.Styleguide, _irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Quickfix],
59
+ badExample: `DATA(msg) = \`Hello \` && name && \`!\`.`,
60
+ goodExample: `DATA(msg) = |Hello { name }!|.`,
61
+ };
62
+ }
63
+ getConfig() {
64
+ return this.conf;
65
+ }
66
+ setConfig(conf) {
67
+ this.conf = conf;
68
+ }
69
+ runParsed(file) {
70
+ const issues = [];
71
+ const structure = file.getStructure();
72
+ if (structure === undefined) {
73
+ return [];
74
+ }
75
+ const allSources = structure.findAllExpressions(Expressions.Source);
76
+ // collect all Source nodes that appear as the RHS (children[2]) of a && expression
77
+ const rhsChildren = new Set();
78
+ for (const source of allSources) {
79
+ const children = source.getChildren();
80
+ if (children.length === 3 && children[1].getFirstToken().getStr() === "&&") {
81
+ rhsChildren.add(children[2]);
82
+ }
83
+ }
84
+ for (const source of allSources) {
85
+ const children = source.getChildren();
86
+ if (children.length !== 3) {
87
+ continue;
88
+ }
89
+ if (children[1].getFirstToken().getStr() !== "&&") {
90
+ continue;
91
+ }
92
+ // only process top-level && roots (not nested RHS children)
93
+ if (rhsChildren.has(source)) {
94
+ continue;
95
+ }
96
+ const operands = this.flattenChain(source);
97
+ if (!operands.some(op => { const c = op.getFirstToken().getStr()[0]; return c === "'" || c === "`"; })) {
98
+ continue;
99
+ }
100
+ const template = this.buildTemplate(operands);
101
+ const start = source.getFirstToken().getStart();
102
+ const end = source.getLastToken().getEnd();
103
+ const fix = edit_helper_1.EditHelper.replaceRange(file, start, end, template);
104
+ const token = children[1].getFirstToken();
105
+ issues.push(issue_1.Issue.atToken(file, token, "Use string template instead of &&", this.getMetadata().key, this.conf.severity, fix));
106
+ }
107
+ return issues;
108
+ }
109
+ flattenChain(node) {
110
+ const children = node.getChildren();
111
+ if (children.length === 3 && children[1].getFirstToken().getStr() === "&&") {
112
+ return [...this.flattenChain(children[0]), ...this.flattenChain(children[2])];
113
+ }
114
+ if (children.length === 1 && children[0] instanceof nodes_1.ExpressionNode) {
115
+ return this.flattenChain(children[0]);
116
+ }
117
+ return [node];
118
+ }
119
+ buildTemplate(operands) {
120
+ let inner = "";
121
+ for (const op of operands) {
122
+ const raw = op.getFirstToken().getStr();
123
+ const firstChar = raw[0];
124
+ if (firstChar === "'" || firstChar === "`") {
125
+ inner += raw.slice(1, -1);
126
+ }
127
+ else {
128
+ inner += `{ ${op.concatTokens()} }`;
129
+ }
130
+ }
131
+ return `|${inner}|`;
132
+ }
133
+ }
134
+ exports.PreferStringTemplate = PreferStringTemplate;
135
+ //# sourceMappingURL=prefer_string_template.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/core",
3
- "version": "2.120.56",
3
+ "version": "2.120.58",
4
4
  "description": "abaplint - Core API",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/abaplint.d.ts",