@abaplint/core 2.96.1 → 2.97.0

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.
@@ -401,6 +401,7 @@ declare namespace BasicTypes {
401
401
  IStructureComponent,
402
402
  StructureType,
403
403
  TableAccessType,
404
+ TableKeyType,
404
405
  ITableKey,
405
406
  ITableOptions,
406
407
  TableType_2 as TableType,
@@ -3626,13 +3627,14 @@ declare interface ISyntaxSettings {
3626
3627
 
3627
3628
  declare type ITableKey = {
3628
3629
  name: string;
3629
- type?: TableAccessType;
3630
+ type: TableAccessType;
3630
3631
  keyFields: string[];
3631
3632
  isUnique: boolean;
3632
3633
  };
3633
3634
 
3634
3635
  declare type ITableOptions = {
3635
3636
  withHeader: boolean;
3637
+ keyType: TableKeyType;
3636
3638
  primaryKey?: ITableKey;
3637
3639
  secondary?: ITableKey[];
3638
3640
  };
@@ -5959,6 +5961,12 @@ declare class TableExpression extends Expression {
5959
5961
  getRunnable(): IStatementRunnable;
5960
5962
  }
5961
5963
 
5964
+ declare enum TableKeyType {
5965
+ default = "DEFAULT",
5966
+ user = "USER",
5967
+ empty = "EMPTY"
5968
+ }
5969
+
5962
5970
  declare class Tables implements IStatement {
5963
5971
  getMatcher(): IStatementRunnable;
5964
5972
  }
@@ -388,7 +388,7 @@ BuiltIn.methods = [
388
388
  {
389
389
  name: "CONCAT_LINES_OF",
390
390
  mandatory: {
391
- "table": new basic_1.TableType(new basic_1.AnyType(), { withHeader: false }),
391
+ "table": new basic_1.TableType(new basic_1.AnyType(), { withHeader: false, keyType: basic_1.TableKeyType.default }),
392
392
  },
393
393
  optional: {
394
394
  "sep": new basic_1.StringType(),
@@ -684,7 +684,7 @@ BuiltIn.methods = [
684
684
  {
685
685
  name: "LINES",
686
686
  mandatory: {
687
- "val": new basic_1.TableType(new basic_1.AnyType(), { withHeader: false }),
687
+ "val": new basic_1.TableType(new basic_1.AnyType(), { withHeader: false, keyType: basic_1.TableKeyType.default }),
688
688
  },
689
689
  return: new basic_1.IntegerType(),
690
690
  },
@@ -90,10 +90,10 @@ class Procedural {
90
90
  }
91
91
  if (param.direction === types_1.FunctionModuleParameterDirection.tables) {
92
92
  if (found instanceof basic_1.TableType) {
93
- found = new basic_1.TableType(found.getRowType(), { withHeader: true });
93
+ found = new basic_1.TableType(found.getRowType(), { withHeader: true, keyType: basic_1.TableKeyType.default });
94
94
  }
95
95
  else {
96
- found = new basic_1.TableType(found, { withHeader: true });
96
+ found = new basic_1.TableType(found, { withHeader: true, keyType: basic_1.TableKeyType.default });
97
97
  }
98
98
  }
99
99
  if ((found instanceof basic_1.UnknownType || found instanceof basic_1.VoidType) && ((_a = param.type) === null || _a === void 0 ? void 0 : _a.includes("-"))) {
@@ -143,7 +143,7 @@ class BasicTypes {
143
143
  }
144
144
  else if (child.concatTokens() === "[]") {
145
145
  if (type instanceof Types.TableType) {
146
- type = new basic_1.TableType(type.getRowType(), { withHeader: false });
146
+ type = new basic_1.TableType(type.getRowType(), { withHeader: false, keyType: Types.TableKeyType.default });
147
147
  }
148
148
  }
149
149
  else { // field name
@@ -295,7 +295,7 @@ class BasicTypes {
295
295
  const isNamed = (firstKey === null || firstKey === void 0 ? void 0 : firstKey.findDirectExpression(expressions_1.Field)) !== undefined;
296
296
  const primaryKey = {
297
297
  name: "primary_key",
298
- type: type,
298
+ type: type || basic_1.TableAccessType.standard,
299
299
  isUnique: isNamed ? false : (firstKey === null || firstKey === void 0 ? void 0 : firstKey.concatTokens().toUpperCase().includes("WITH UNIQUE ")) === true,
300
300
  keyFields: [],
301
301
  };
@@ -304,9 +304,6 @@ class BasicTypes {
304
304
  for (const k of (firstKey === null || firstKey === void 0 ? void 0 : firstKey.findDirectExpressions(expressions_1.FieldSub)) || []) {
305
305
  primaryKey.keyFields.push(k.concatTokens().toUpperCase());
306
306
  }
307
- if (primaryKey.keyFields.length === 0 && text.includes(" DEFAULT KEY")) {
308
- primaryKey.keyFields.push("TABLE_LINE");
309
- }
310
307
  }
311
308
  else {
312
309
  start = 0;
@@ -329,8 +326,16 @@ class BasicTypes {
329
326
  }
330
327
  secondaryKeys.push(secondary);
331
328
  }
329
+ let keyType = Types.TableKeyType.user;
330
+ if (text.includes(" EMPTY KEY")) {
331
+ keyType = Types.TableKeyType.empty;
332
+ }
333
+ else if (text.includes(" DEFAULT KEY")) {
334
+ keyType = Types.TableKeyType.default;
335
+ }
332
336
  const options = {
333
337
  withHeader: text.includes(" WITH HEADER LINE"),
338
+ keyType: keyType,
334
339
  primaryKey: primaryKey,
335
340
  secondary: secondaryKeys,
336
341
  };
@@ -477,7 +482,7 @@ class BasicTypes {
477
482
  || text === "TYPE HASHED TABLE"
478
483
  || text === "TYPE INDEX TABLE"
479
484
  || text === "TYPE ANY TABLE") {
480
- return new Types.TableType(new Types.AnyType(), { withHeader: node.concatTokens().toUpperCase().includes("WITH HEADER LINE") });
485
+ return new Types.TableType(new Types.AnyType(), { withHeader: node.concatTokens().toUpperCase().includes("WITH HEADER LINE"), keyType: Types.TableKeyType.default });
481
486
  }
482
487
  else if (text.startsWith("LIKE ")) {
483
488
  let sub = node.findFirstExpression(Expressions.Type);
@@ -495,7 +500,7 @@ class BasicTypes {
495
500
  }
496
501
  found = this.resolveLikeName(sub);
497
502
  if (found && this.isOccurs(node)) {
498
- found = new Types.TableType(found, { withHeader: text.includes("WITH HEADER LINE") }, qualifiedName);
503
+ found = new Types.TableType(found, { withHeader: text.includes("WITH HEADER LINE"), keyType: Types.TableKeyType.default }, qualifiedName);
499
504
  }
500
505
  }
501
506
  else if (text.startsWith("TYPE LINE OF ")) {
@@ -524,17 +529,17 @@ class BasicTypes {
524
529
  found = this.resolveTypeName(typeName, this.findLength(node), this.findDecimals(node), qualifiedName);
525
530
  const concat = node.concatTokens().toUpperCase();
526
531
  if (found && this.isOccurs(node)) {
527
- found = new Types.TableType(found, { withHeader: concat.includes(" WITH HEADER LINE") }, qualifiedName);
532
+ found = new Types.TableType(found, { withHeader: concat.includes(" WITH HEADER LINE"), keyType: Types.TableKeyType.default }, qualifiedName);
528
533
  }
529
534
  else if (found && concat.includes(" WITH HEADER LINE")) {
530
535
  if (found instanceof Types.VoidType) {
531
- found = new Types.TableType(found, { withHeader: true });
536
+ found = new Types.TableType(found, { withHeader: true, keyType: Types.TableKeyType.default });
532
537
  }
533
538
  else if (!(found instanceof Types.TableType)) {
534
539
  throw new Error("WITH HEADER LINE can only be used with internal table");
535
540
  }
536
541
  else {
537
- found = new Types.TableType(found.getRowType(), { withHeader: true });
542
+ found = new Types.TableType(found.getRowType(), { withHeader: true, keyType: Types.TableKeyType.default });
538
543
  }
539
544
  }
540
545
  if (found === undefined && typeName === undefined) {
@@ -548,7 +553,7 @@ class BasicTypes {
548
553
  }
549
554
  found = new Types.CharacterType(length, { qualifiedName: qualifiedName }); // fallback
550
555
  if (this.isOccurs(node)) {
551
- found = new Types.TableType(found, { withHeader: concat.includes(" WITH HEADER LINE") }, qualifiedName);
556
+ found = new Types.TableType(found, { withHeader: concat.includes(" WITH HEADER LINE"), keyType: Types.TableKeyType.default }, qualifiedName);
552
557
  }
553
558
  }
554
559
  }
@@ -32,7 +32,7 @@ class Controls {
32
32
  { name: "LINE_SELECTOR", type: new basic_1.CharacterType(1) },
33
33
  { name: "H_GRID", type: new basic_1.CharacterType(1) },
34
34
  { name: "V_GRID", type: new basic_1.CharacterType(1) },
35
- { name: "COLS", type: new basic_1.TableType(cols, { withHeader: false }) },
35
+ { name: "COLS", type: new basic_1.TableType(cols, { withHeader: false, keyType: basic_1.TableKeyType.default }) },
36
36
  { name: "INVISIBLE", type: new basic_1.CharacterType(1) },
37
37
  ]);
38
38
  const id = new _typed_identifier_1.TypedIdentifier(token, filename, type);
@@ -21,13 +21,13 @@ class Find {
21
21
  { name: "LINE", type: new basic_1.IntegerType() },
22
22
  { name: "OFFSET", type: new basic_1.IntegerType() },
23
23
  { name: "LENGTH", type: new basic_1.IntegerType() },
24
- { name: "SUBMATCHES", type: new basic_1.TableType(sub, { withHeader: false }) },
24
+ { name: "SUBMATCHES", type: new basic_1.TableType(sub, { withHeader: false, keyType: basic_1.TableKeyType.default }) },
25
25
  ], "MATCH_RESULT", "MATCH_RESULT");
26
26
  if (node.concatTokens().toUpperCase().startsWith("FIND FIRST")) {
27
27
  this.inline(rfound, scope, filename, type);
28
28
  }
29
29
  else {
30
- this.inline(rfound, scope, filename, new basic_1.TableType(type, { withHeader: false }, "MATCH_RESULT_TAB"));
30
+ this.inline(rfound, scope, filename, new basic_1.TableType(type, { withHeader: false, keyType: basic_1.TableKeyType.default }, "MATCH_RESULT_TAB"));
31
31
  }
32
32
  }
33
33
  const ofound = node.findExpressionsAfterToken("OFFSET");
@@ -21,7 +21,7 @@ class Ranges {
21
21
  { name: "low", type: found },
22
22
  { name: "high", type: found },
23
23
  ]);
24
- const type = new basic_1.TableType(structure, { withHeader: true });
24
+ const type = new basic_1.TableType(structure, { withHeader: true, keyType: basic_1.TableKeyType.default });
25
25
  const id = new _typed_identifier_1.TypedIdentifier(nameToken, filename, type);
26
26
  scope.addIdentifier(id);
27
27
  }
@@ -39,7 +39,7 @@ class SelectOption {
39
39
  { name: "LOW", type: found },
40
40
  { name: "HIGH", type: found },
41
41
  ]);
42
- scope.addIdentifier(new _typed_identifier_1.TypedIdentifier(nameToken, filename, new basic_1.TableType(stru, { withHeader: true })));
42
+ scope.addIdentifier(new _typed_identifier_1.TypedIdentifier(nameToken, filename, new basic_1.TableType(stru, { withHeader: true, keyType: basic_1.TableKeyType.default })));
43
43
  return;
44
44
  }
45
45
  if (nameToken) {
@@ -10,7 +10,7 @@ const _type_utils_1 = require("../_type_utils");
10
10
  class Split {
11
11
  runSyntax(node, scope, filename) {
12
12
  const intoTable = node.findTokenSequencePosition("INTO", "TABLE") !== undefined;
13
- const type = intoTable ? new basic_1.TableType(new basic_1.StringType(), { withHeader: false }) : new basic_1.StringType();
13
+ const type = intoTable ? new basic_1.TableType(new basic_1.StringType(), { withHeader: false, keyType: basic_1.TableKeyType.default }) : new basic_1.StringType();
14
14
  for (const target of node.findAllExpressions(Expressions.Target)) {
15
15
  const inline = target.findDirectExpression(Expressions.InlineData);
16
16
  if (inline) {
@@ -58,7 +58,7 @@ class Data {
58
58
  }
59
59
  if (found instanceof Basic.VoidType) {
60
60
  if (table === true) {
61
- return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(found, { withHeader: true }));
61
+ return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(found, { withHeader: true, keyType: Basic.TableKeyType.default }));
62
62
  }
63
63
  else {
64
64
  return new _typed_identifier_1.TypedIdentifier(name, filename, found);
@@ -76,7 +76,7 @@ class Data {
76
76
  }
77
77
  }
78
78
  if (table === true) {
79
- return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(new Basic.StructureType(components), { withHeader: true }));
79
+ return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(new Basic.StructureType(components), { withHeader: true, keyType: Basic.TableKeyType.default }));
80
80
  }
81
81
  else {
82
82
  const val = Object.keys(values).length > 0 ? values : undefined;
@@ -49,7 +49,7 @@ class Statics {
49
49
  }
50
50
  if (found instanceof Basic.VoidType) {
51
51
  if (table === true) {
52
- return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(found, { withHeader: true }));
52
+ return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(found, { withHeader: true, keyType: Basic.TableKeyType.default }));
53
53
  }
54
54
  else {
55
55
  return new _typed_identifier_1.TypedIdentifier(name, filename, found);
@@ -67,7 +67,7 @@ class Statics {
67
67
  }
68
68
  }
69
69
  if (table === true) {
70
- return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(new Basic.StructureType(components), { withHeader: true }));
70
+ return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.TableType(new Basic.StructureType(components), { withHeader: true, keyType: Basic.TableKeyType.default }));
71
71
  }
72
72
  else {
73
73
  return new _typed_identifier_1.TypedIdentifier(name, filename, new Basic.StructureType(components));
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TableType = exports.TableAccessType = void 0;
3
+ exports.TableType = exports.TableKeyType = exports.TableAccessType = void 0;
4
4
  const _abstract_type_1 = require("./_abstract_type");
5
5
  var TableAccessType;
6
6
  (function (TableAccessType) {
@@ -10,6 +10,12 @@ var TableAccessType;
10
10
  TableAccessType["index"] = "INDEX";
11
11
  TableAccessType["any"] = "ANY";
12
12
  })(TableAccessType = exports.TableAccessType || (exports.TableAccessType = {}));
13
+ var TableKeyType;
14
+ (function (TableKeyType) {
15
+ TableKeyType["default"] = "DEFAULT";
16
+ TableKeyType["user"] = "USER";
17
+ TableKeyType["empty"] = "EMPTY";
18
+ })(TableKeyType = exports.TableKeyType || (exports.TableKeyType = {}));
13
19
  class TableType extends _abstract_type_1.AbstractType {
14
20
  constructor(rowType, options, qualifiedName) {
15
21
  var _a;
@@ -49,6 +55,7 @@ class TableType extends _abstract_type_1.AbstractType {
49
55
  isGeneric() {
50
56
  var _a, _b;
51
57
  if (((_a = this.options.primaryKey) === null || _a === void 0 ? void 0 : _a.type) !== TableAccessType.standard
58
+ && this.options.keyType === TableKeyType.user
52
59
  && ((_b = this.options.primaryKey) === null || _b === void 0 ? void 0 : _b.keyFields.length) === 0) {
53
60
  return true;
54
61
  }
@@ -49,10 +49,10 @@ class FormDefinition extends _identifier_1.Identifier {
49
49
  let type = p.getType();
50
50
  const isStructure = param.findDirectTokenByText("STRUCTURE") !== undefined;
51
51
  if (isStructure) {
52
- type = new basic_1.TableType(type, { withHeader: true });
52
+ type = new basic_1.TableType(type, { withHeader: true, keyType: basic_1.TableKeyType.default });
53
53
  }
54
54
  if (type instanceof basic_1.TableType) {
55
- type = new basic_1.TableType(type.getRowType(), { withHeader: true });
55
+ type = new basic_1.TableType(type.getRowType(), { withHeader: true, keyType: basic_1.TableKeyType.default });
56
56
  }
57
57
  else if (!(type instanceof basic_1.UnknownType) && !(type instanceof basic_1.VoidType)) {
58
58
  type = new basic_1.UnknownType("FORM TABLES type must be table type");
package/build/src/ddic.js CHANGED
@@ -66,7 +66,7 @@ class DDIC {
66
66
  case "%_C_POINTER":
67
67
  return new Types.HexType(8, qualifiedName);
68
68
  case "TABLE":
69
- return new Types.TableType(new Types.AnyType(), { withHeader: false });
69
+ return new Types.TableType(new Types.AnyType(), { withHeader: false, keyType: Types.TableKeyType.default });
70
70
  case "DATA":
71
71
  return new Types.AnyType({ qualifiedName: qualifiedName });
72
72
  case "NUMERIC":
@@ -32,6 +32,7 @@ class TableType extends _abstract_object_1.AbstractObject {
32
32
  var _a, _b, _c;
33
33
  const tableOptions = {
34
34
  withHeader: false,
35
+ keyType: Types.TableKeyType.user,
35
36
  secondary: [],
36
37
  };
37
38
  for (const k of ((_a = this.parsedXML) === null || _a === void 0 ? void 0 : _a.dd43v) || []) {
@@ -63,7 +63,7 @@ class Registry {
63
63
  }
64
64
  static abaplintVersion() {
65
65
  // magic, see build script "version.sh"
66
- return "2.96.1";
66
+ return "2.97.0";
67
67
  }
68
68
  getDDICReferences() {
69
69
  return this.references;
@@ -24,7 +24,7 @@ class ConstantClasses {
24
24
  title: "Validate constant classes",
25
25
  shortDescription: `Checks that a class contains exactly the constants corresponding to a domain's fixed values.`,
26
26
  extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-enumeration-classes-to-constants-interfaces`,
27
- tags: [_irule_1.RuleTag.Styleguide, _irule_1.RuleTag.Experimental],
27
+ tags: [_irule_1.RuleTag.Styleguide],
28
28
  };
29
29
  }
30
30
  initialize(reg) {
@@ -122,7 +122,7 @@ class Downport {
122
122
  return {
123
123
  key: "downport",
124
124
  title: "Downport statement",
125
- shortDescription: `Experimental downport functionality`,
125
+ shortDescription: `Downport functionality`,
126
126
  extendedInformation: `Much like the 'commented_code' rule this rule loops through unknown statements and tries parsing with
127
127
  a higher level language version. If successful, various rules are applied to downport the statement.
128
128
  Target downport version is always v702, thus rule is only enabled if target version is v702.
@@ -155,7 +155,7 @@ Current rules:
155
155
  * MESSAGE with non simple source
156
156
 
157
157
  Only one transformation is applied to a statement at a time, so multiple steps might be required to do the full downport.`,
158
- tags: [_irule_1.RuleTag.Experimental, _irule_1.RuleTag.Downport, _irule_1.RuleTag.Quickfix],
158
+ tags: [_irule_1.RuleTag.Downport, _irule_1.RuleTag.Quickfix],
159
159
  };
160
160
  }
161
161
  getConfig() {
@@ -142,6 +142,7 @@ __exportStar(require("./space_before_dot"), exports);
142
142
  __exportStar(require("./sql_escape_host_variables"), exports);
143
143
  __exportStar(require("./start_at_tab"), exports);
144
144
  __exportStar(require("./static_call_via_instance"), exports);
145
+ __exportStar(require("./strict_sql"), exports);
145
146
  __exportStar(require("./superclass_final"), exports);
146
147
  __exportStar(require("./superfluous_value"), exports);
147
148
  __exportStar(require("./sy_modification"), exports);
@@ -8,6 +8,7 @@ const _abap_rule_1 = require("./_abap_rule");
8
8
  const _basic_rule_config_1 = require("./_basic_rule_config");
9
9
  const version_1 = require("../version");
10
10
  const _irule_1 = require("./_irule");
11
+ const edit_helper_1 = require("../edit_helper");
11
12
  class SQLEscapeHostVariablesConf extends _basic_rule_config_1.BasicRuleConfig {
12
13
  }
13
14
  exports.SQLEscapeHostVariablesConf = SQLEscapeHostVariablesConf;
@@ -22,7 +23,7 @@ class SQLEscapeHostVariables extends _abap_rule_1.ABAPRule {
22
23
  title: "Escape SQL host variables",
23
24
  shortDescription: `Escape SQL host variables, from 740sp05`,
24
25
  extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#avoid-obsolete-language-elements`,
25
- tags: [_irule_1.RuleTag.Upport, _irule_1.RuleTag.Styleguide],
26
+ tags: [_irule_1.RuleTag.Upport, _irule_1.RuleTag.Styleguide, _irule_1.RuleTag.Quickfix, _irule_1.RuleTag.Syntax],
26
27
  badExample: `SELECT * FROM tab INTO TABLE res WHERE field = val.`,
27
28
  goodExample: `SELECT * FROM tab INTO TABLE @res WHERE field = @val.`,
28
29
  };
@@ -35,52 +36,45 @@ class SQLEscapeHostVariables extends _abap_rule_1.ABAPRule {
35
36
  }
36
37
  runParsed(file, obj) {
37
38
  const issues = [];
38
- if (obj.getType() === "INTF") {
39
+ const type = obj.getType();
40
+ if (type === "INTF" || type === "TYPE") {
39
41
  return [];
40
42
  }
41
- if (this.reg.getConfig().getVersion() < version_1.Version.v740sp02 && this.reg.getConfig().getVersion() !== version_1.Version.Cloud) {
43
+ if (this.reg.getConfig().getVersion() < version_1.Version.v740sp02
44
+ && this.reg.getConfig().getVersion() !== version_1.Version.Cloud) {
42
45
  return [];
43
46
  }
44
47
  for (const s of file.getStatements()) {
45
- const str = s.concatTokens().toUpperCase();
46
- if (s.get() instanceof Statements.Select
47
- || s.get() instanceof Statements.SelectLoop) {
48
- // this is not completely correct and does not catch all, but okay for now
49
- // todo: replace with logic from "else if" branch below, when/if it proves to work
50
- if (str.includes(" INTO ( @")
51
- || str.includes(" INTO (@")
52
- || str.includes(" INTO @")
53
- || str.includes(" INTO TABLE @")
54
- || str.includes(" INTO CORRESPONDING FIELDS OF @")
55
- || str.includes(" INTO CORRESPONDING FIELDS OF TABLE @")
56
- || str.includes(" APPENDING TABLE @")
57
- || (str.includes(" APPENDING ") === false && str.includes(" INTO ") === false)
58
- || str.includes(" APPENDING CORRESPONDING FIELDS OF TABLE @")) {
59
- continue;
60
- }
61
- else {
62
- const message = "Escape SQL host variables";
63
- const issue = issue_1.Issue.atToken(file, s.getFirstToken(), message, this.getMetadata().key, this.conf.severity);
64
- issues.push(issue);
65
- }
66
- }
67
- else if (s.get() instanceof Statements.UpdateDatabase
48
+ if (s.get() instanceof Statements.UpdateDatabase
68
49
  || s.get() instanceof Statements.ModifyDatabase
50
+ || s.get() instanceof Statements.Select
51
+ || s.get() instanceof Statements.SelectLoop
69
52
  || s.get() instanceof Statements.InsertDatabase
70
53
  || s.get() instanceof Statements.DeleteDatabase) {
71
- if (str.startsWith("MODIFY SCREEN FROM ")) {
72
- continue;
73
- }
74
54
  for (const o of s.findAllExpressions(Expressions.SQLSource)) {
75
55
  const first = o.getFirstChild();
76
56
  if (((first === null || first === void 0 ? void 0 : first.get()) instanceof Expressions.Source && first.getChildren()[0].get() instanceof Expressions.FieldChain)
77
57
  || ((first === null || first === void 0 ? void 0 : first.get()) instanceof Expressions.SimpleSource3 && first.getChildren()[0].get() instanceof Expressions.FieldChain)) {
78
58
  const message = "Escape SQL host variables";
79
- const issue = issue_1.Issue.atToken(file, first.getFirstToken(), message, this.getMetadata().key, this.conf.severity);
59
+ const firstToken = o.getFirstChild().getFirstToken();
60
+ const fix = edit_helper_1.EditHelper.replaceToken(file, firstToken, "@" + (firstToken === null || firstToken === void 0 ? void 0 : firstToken.getStr()));
61
+ const issue = issue_1.Issue.atToken(file, first.getFirstToken(), message, this.getMetadata().key, this.conf.severity, fix);
80
62
  issues.push(issue);
81
63
  break;
82
64
  }
83
65
  }
66
+ for (const o of s.findAllExpressions(Expressions.SQLTarget)) {
67
+ const escaped = o.findDirectTokenByText("@");
68
+ if (escaped !== undefined) {
69
+ continue;
70
+ }
71
+ const message = "Escape SQL host variables";
72
+ const firstToken = o.getFirstChild().getFirstToken();
73
+ const fix = edit_helper_1.EditHelper.replaceToken(file, firstToken, "@" + (firstToken === null || firstToken === void 0 ? void 0 : firstToken.getStr()));
74
+ const issue = issue_1.Issue.atToken(file, o.getFirstToken(), message, this.getMetadata().key, this.conf.severity, fix);
75
+ issues.push(issue);
76
+ break;
77
+ }
84
78
  }
85
79
  }
86
80
  return issues;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StrictSQL = exports.StrictSQLConf = void 0;
4
+ const Statements = require("../abap/2_statements/statements");
5
+ const Expressions = require("../abap/2_statements/expressions");
6
+ const issue_1 = require("../issue");
7
+ const _abap_rule_1 = require("./_abap_rule");
8
+ const _basic_rule_config_1 = require("./_basic_rule_config");
9
+ const version_1 = require("../version");
10
+ const _irule_1 = require("./_irule");
11
+ class StrictSQLConf extends _basic_rule_config_1.BasicRuleConfig {
12
+ }
13
+ exports.StrictSQLConf = StrictSQLConf;
14
+ class StrictSQL extends _abap_rule_1.ABAPRule {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.conf = new StrictSQLConf();
18
+ }
19
+ getMetadata() {
20
+ return {
21
+ key: "strict_sql",
22
+ title: "Strict SQL",
23
+ shortDescription: `Strict SQL`,
24
+ extendedInformation: `https://help.sap.com/doc/abapdocu_751_index_htm/7.51/en-US/abapinto_clause.htm
25
+
26
+ https://help.sap.com/doc/abapdocu_751_index_htm/7.51/en-us/abenopensql_strict_mode_750.htm
27
+
28
+ Also see separate rule sql_escape_host_variables`,
29
+ tags: [_irule_1.RuleTag.Upport, _irule_1.RuleTag.Syntax],
30
+ };
31
+ }
32
+ getConfig() {
33
+ return this.conf;
34
+ }
35
+ setConfig(conf) {
36
+ this.conf = conf;
37
+ }
38
+ runParsed(file, obj) {
39
+ const issues = [];
40
+ const type = obj.getType();
41
+ if (type === "INTF" || type === "TYPE") {
42
+ return [];
43
+ }
44
+ if (this.reg.getConfig().getVersion() < version_1.Version.v740sp02
45
+ && this.reg.getConfig().getVersion() !== version_1.Version.Cloud) {
46
+ return [];
47
+ }
48
+ for (const s of file.getStatements()) {
49
+ if (s.get() instanceof Statements.Select
50
+ || s.get() instanceof Statements.SelectLoop) {
51
+ const expr = s.findDirectExpression(Expressions.Select);
52
+ const where = expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLCond);
53
+ const into = (expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLIntoStructure))
54
+ || (expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLIntoTable));
55
+ if (into === undefined || where === undefined) {
56
+ continue;
57
+ }
58
+ if (where.getFirstToken().getStart().isBefore(into.getFirstToken().getStart())) {
59
+ continue;
60
+ }
61
+ const message = "INTO/APPENDING must be last in strict SQL";
62
+ const issue = issue_1.Issue.atToken(file, s.getFirstToken(), message, this.getMetadata().key, this.conf.severity);
63
+ issues.push(issue);
64
+ break;
65
+ }
66
+ }
67
+ return issues;
68
+ }
69
+ }
70
+ exports.StrictSQL = StrictSQL;
71
+ //# sourceMappingURL=strict_sql.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/core",
3
- "version": "2.96.1",
3
+ "version": "2.97.0",
4
4
  "description": "abaplint - Core API",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/abaplint.d.ts",