@abaplint/core
Version:
abaplint - Core API
1,056 lines (1,052 loc) • 160 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Downport = exports.DownportConf = void 0;
/* eslint-disable max-len */
const crypto = require("crypto");
const tokens_1 = require("../abap/1_lexer/tokens");
const Expressions = require("../abap/2_statements/expressions");
const Statements = require("../abap/2_statements/statements");
const statements_1 = require("../abap/2_statements/statements");
const _statement_1 = require("../abap/2_statements/statements/_statement");
const Structures = require("../abap/3_structures/structures");
const _builtin_1 = require("../abap/5_syntax/_builtin");
const _reference_1 = require("../abap/5_syntax/_reference");
const _scope_type_1 = require("../abap/5_syntax/_scope_type");
const syntax_1 = require("../abap/5_syntax/syntax");
const nodes_1 = require("../abap/nodes");
const _typed_identifier_1 = require("../abap/types/_typed_identifier");
const basic_1 = require("../abap/types/basic");
const config_1 = require("../config");
const edit_helper_1 = require("../edit_helper");
const issue_1 = require("../issue");
const objects_1 = require("../objects");
const _abap_object_1 = require("../objects/_abap_object");
const position_1 = require("../position");
const registry_1 = require("../registry");
const include_graph_1 = require("../utils/include_graph");
const version_1 = require("../version");
const virtual_position_1 = require("../virtual_position");
const _basic_rule_config_1 = require("./_basic_rule_config");
const _irule_1 = require("./_irule");
// todo: refactor each sub-rule to new classes?
// todo: add configuration
class DownportConf extends _basic_rule_config_1.BasicRuleConfig {
}
exports.DownportConf = DownportConf;
class SkipToNextFile extends Error {
constructor(issue) {
super();
this.issue = issue;
}
}
class SpagHelper {
constructor(spag) {
this.spag = spag;
}
renameVariable(oldName, pos, lowFile, newName) {
let fix = undefined;
const references = this.findReferences(oldName, pos);
references.sort((a, b) => {
if (a.start.equals(b.start)) {
return 0;
}
return a.start.isAfter(b.start) ? 1 : -1;
});
for (const r of references) {
const replace = edit_helper_1.EditHelper.replaceRange(lowFile, r.start, r.end, newName);
if (fix === undefined) {
fix = replace;
}
else {
fix = edit_helper_1.EditHelper.merge(replace, fix);
}
}
return fix;
}
findReferences(name, pos) {
var _a, _b;
const positions = [];
function has(element) {
return positions.some(a => a.start.equals(element.start));
}
for (const r of this.spag.getData().references) {
if (((_a = r.resolved) === null || _a === void 0 ? void 0 : _a.getName()) === name && ((_b = r.resolved) === null || _b === void 0 ? void 0 : _b.getStart().equals(pos))) {
const sub = {
start: r.position.getStart(),
end: r.position.getEnd(),
};
if (has(sub) === false) {
positions.push(sub);
}
}
}
for (const child of this.spag.getChildren()) {
const subPositions = new SpagHelper(child).findReferences(name, pos);
for (const sub of subPositions) {
if (has(sub) === false) {
positions.push(sub);
}
}
}
return positions;
}
findRecursiveDuplicate(name, skip) {
var _a;
const found = this.spag.findVariable(name);
if ((found === null || found === void 0 ? void 0 : found.getStart().equals(skip)) === false) {
return found;
}
for (const child of ((_a = this.spag) === null || _a === void 0 ? void 0 : _a.getChildren()) || []) {
const sub = new SpagHelper(child).findRecursiveDuplicate(name, skip);
if (sub) {
return sub;
}
}
return undefined;
}
isDuplicateName(name, pos) {
let parent = this.spag.getParent();
while ((parent === null || parent === void 0 ? void 0 : parent.getIdentifier().stype) === _scope_type_1.ScopeType.Let
|| (parent === null || parent === void 0 ? void 0 : parent.getIdentifier().stype) === _scope_type_1.ScopeType.For) {
parent = parent.getParent();
}
if (parent === undefined) {
return undefined;
}
return new SpagHelper(parent).findRecursiveDuplicate(name, pos) !== undefined;
}
}
class Downport {
constructor() {
this.conf = new DownportConf();
}
getMetadata() {
return {
key: "downport",
title: "Downport statement",
shortDescription: `Downport functionality`,
extendedInformation: `Much like the 'commented_code' rule this rule loops through unknown statements and tries parsing with
a higher level language version. If successful, various rules are applied to downport the statement.
Target downport version is always v702, thus rule is only enabled if target version is v702.
Current rules:
* NEW transformed to CREATE OBJECT, opposite of https://rules.abaplint.org/use_new/
* DATA() definitions are outlined, opposite of https://rules.abaplint.org/prefer_inline/
* FIELD-SYMBOL() definitions are outlined
* CONV is outlined
* COND is outlined
* REDUCE is outlined
* SWITCH is outlined
* FILTER is outlined
* APPEND expression is outlined
* INSERT expression is outlined
* EMPTY KEY is changed to DEFAULT KEY, opposite of DEFAULT KEY in https://rules.abaplint.org/avoid_use/
* CAST changed to ?=
* LOOP AT method_call( ) is outlined
* VALUE # with structure fields
* VALUE # with internal table lines
* Table Expressions are outlined
* SELECT INTO @DATA definitions are outlined
* Some occurrences of string template formatting option ALPHA changed to function module call
* SELECT/INSERT/MODIFY/DELETE/UPDATE "," in field list removed, "@" in source/targets removed
* PARTIALLY IMPLEMENTED removed, it can be quick fixed via rule implement_methods
* RAISE EXCEPTION ... MESSAGE
* Moving with +=, -=, /=, *=, &&= is expanded
* line_exists and line_index is downported to READ TABLE
* ENUMs, but does not nessesarily give the correct type and value
* MESSAGE with non simple source
Only one transformation is applied to a statement at a time, so multiple steps might be required to do the full downport.
Make sure to test the downported code, it might not always be completely correct.`,
tags: [_irule_1.RuleTag.Downport, _irule_1.RuleTag.Quickfix],
};
}
getConfig() {
return this.conf;
}
setConfig(conf) {
this.conf = conf;
}
initialize(reg) {
this.lowReg = reg;
const version = this.lowReg.getConfig().getVersion();
if (version === version_1.Version.v702 || version === version_1.Version.OpenABAP) {
this.initHighReg();
}
return this;
}
listMainForInclude(filename) {
if (filename === undefined) {
return [];
}
// only initialize this.graph if needed
if (this.graph === undefined) {
this.graph = new include_graph_1.IncludeGraph(this.lowReg);
}
return this.graph.listMainForInclude(filename);
}
containsError(highObj) {
for (const file of highObj.getABAPFiles()) {
for (const statement of file.getStatements()) {
if (statement.get() instanceof _statement_1.Unknown) {
return true; // contains parser errors
}
}
if (file.getStructure() === undefined) {
return true;
}
}
return false;
}
run(lowObj) {
var _a;
const ret = [];
this.counter = 1;
const version = this.lowReg.getConfig().getVersion();
if (version !== version_1.Version.v702 && version !== version_1.Version.OpenABAP) {
return ret;
}
else if (!(lowObj instanceof _abap_object_1.ABAPObject)) {
return ret;
}
const highObj = this.highReg.getObject(lowObj.getType(), lowObj.getName());
if (highObj === undefined || !(highObj instanceof _abap_object_1.ABAPObject)) {
return ret;
}
let highSyntaxObj = highObj;
if (this.containsError(highObj)) {
return ret;
}
// for includes do the syntax check via a main program
if (lowObj instanceof objects_1.Program && lowObj.isInclude()) {
const mains = this.listMainForInclude((_a = lowObj.getMainABAPFile()) === null || _a === void 0 ? void 0 : _a.getFilename());
if (mains.length <= 0) {
return [];
}
const f = this.highReg.getFileByName(mains[0]);
if (f === undefined) {
return [];
}
highSyntaxObj = this.highReg.findObjectForFile(f);
}
for (const lowFile of lowObj.getABAPFiles()) {
let highSyntax = undefined;
const highFile = highObj.getABAPFileByName(lowFile.getFilename());
if (highFile === undefined) {
continue;
}
const lowStatements = lowFile.getStatements();
const highStatements = highFile.getStatements();
if (lowStatements.length !== highStatements.length) {
// after applying a fix, there might be more statements in lowFile
// should highReg be initialized again?
/*
const message = "Internal Error: Statement lengths does not match";
ret.push(Issue.atStatement(lowFile, lowStatements[0], message, this.getMetadata().key));
*/
// hmm, add some way to disable lazyUnknown() in statement_parser.ts
// alternatively explicit enable it in vscode, its only relevant when a user is
// actively editing the files
continue;
}
if (highSyntax === undefined) {
highSyntax = new syntax_1.SyntaxLogic(this.highReg, highSyntaxObj).run();
}
let containsUnknown = false;
for (let i = 0; i < lowStatements.length; i++) {
const low = lowStatements[i];
const high = highStatements[i];
if ((low.get() instanceof _statement_1.Unknown && !(high.get() instanceof _statement_1.Unknown))
|| high.findFirstExpression(Expressions.InlineData)) {
containsUnknown = true;
try {
const issue = this.checkStatement(low, high, lowFile, highSyntax, highFile);
if (issue) {
ret.push(issue);
}
}
catch (e) {
if (e instanceof SkipToNextFile) {
ret.push(e.issue);
break;
}
else {
throw e;
}
}
}
}
if (ret.length === 0 && containsUnknown) {
// this is a hack in order not to change too many unit tests
for (let i = 0; i < lowStatements.length; i++) {
const high = highStatements[i];
if (high.get() instanceof Statements.Data) {
const issue = this.anonymousTableType(high, lowFile, highSyntax);
if (issue) {
ret.push(issue);
}
}
}
}
if (ret.length === 0 && lowFile.getRaw().includes(" xsdbool(")) {
for (let i = 0; i < lowStatements.length; i++) {
const high = highStatements[i];
const issue = this.replaceXsdBool(high, lowFile, highSyntax);
if (issue) {
ret.push(issue);
}
}
}
}
return ret;
}
////////////////////
/** clones the orginal repository into highReg, and parses it with higher language version */
initHighReg() {
// use default configuration, ie. default target version
const highConfig = config_1.Config.getDefault().get();
const lowConfig = this.lowReg.getConfig().get();
highConfig.syntax.errorNamespace = lowConfig.syntax.errorNamespace;
highConfig.syntax.globalConstants = lowConfig.syntax.globalConstants;
highConfig.syntax.globalMacros = lowConfig.syntax.globalMacros;
this.highReg = new registry_1.Registry();
for (const o of this.lowReg.getObjects()) {
for (const f of o.getFiles()) {
if (this.lowReg.isDependency(o) === true) {
this.highReg.addDependency(f);
}
else {
this.highReg.addFile(f);
}
}
}
this.highReg.parse();
}
/** applies one rule at a time, multiple iterations are required to transform complex statements */
checkStatement(low, high, lowFile, highSyntax, highFile) {
if (low.getFirstToken().getStart() instanceof virtual_position_1.VirtualPosition) {
return undefined;
}
// downport XSDBOOL() early, as it is valid 702 syntax
/*
let found = this.replaceXsdBool(high, lowFile, highSyntax);
if (found) {
return found;
}
*/
let found = this.downportEnum(low, high, lowFile, highSyntax, highFile);
if (found) {
return found;
}
found = this.partiallyImplemented(high, lowFile);
if (found) {
return found;
}
found = this.raiseException(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.emptyKey(low, high, lowFile);
if (found) {
return found;
}
found = this.stringTemplateAlpha(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.moveWithOperator(low, high, lowFile);
if (found) {
return found;
}
found = this.moveWithSimpleValue(low, high, lowFile);
if (found) {
return found;
}
found = this.assignWithTable(low, high, lowFile);
if (found) {
return found;
}
found = this.assignComponent(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportRefSimple(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportCorrespondingSimple(high, lowFile);
if (found) {
return found;
}
found = this.downportRef(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportLoopGroup(high, lowFile, highSyntax, highFile);
if (found) {
return found;
}
found = this.callFunctionParameterSimple(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.moveWithTableTarget(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportSelectInline(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportSelectExistence(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportSQLMoveInto(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportSQLExtras(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineLoopInput(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineLoopTarget(high, lowFile, highSyntax);
if (found) {
return found;
}
let skipValue = false;
let skipReduce = false;
const valueBody = high.findFirstExpression(Expressions.ValueBody);
const reduceBody = high.findFirstExpression(Expressions.ReduceBody);
if (valueBody && reduceBody) {
const valueToken = valueBody.getFirstToken();
const reduceToken = reduceBody.getFirstToken();
if (valueToken.getStart().isBefore(reduceToken.getStart())) {
skipReduce = true;
}
else {
skipValue = true;
}
}
if (skipValue !== true) {
found = this.outlineValue(low, high, lowFile, highSyntax);
if (found) {
return found;
}
}
if (skipReduce !== true) {
found = this.outlineReduce(low, high, lowFile, highSyntax);
if (found) {
return found;
}
}
found = this.outlineCorresponding(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportSelectFields(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineSwitch(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineFilter(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineCast(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineConv(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineCond(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineCatchSimple(high, lowFile);
if (found) {
return found;
}
found = this.outlineGetReferenceSimple(high, lowFile);
if (found) {
return found;
}
found = this.outlineDataSimple(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineData(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.outlineFS(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.newToCreateObject(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.replaceLineFunctions(high, lowFile, highSyntax, highFile);
if (found) {
return found;
}
found = this.getReference(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.replaceContains(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.replaceMethodConditional(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.replaceTableExpression(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.replaceAppendExpression(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.replaceInsertExpression(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportMessageSource(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportMessage(high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportReadTable(high, lowFile, highSyntax);
if (found) {
return found;
}
return undefined;
}
//////////////////////////////////////////
/** move INTO from after WHERE to after FROM */
downportSQLMoveInto(low, high, lowFile, _highSyntax) {
var _a;
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
// note: SQLCond is also used in JOIN(FROM) conditions
const where = (_a = high.findFirstExpression(Expressions.Select)) === null || _a === void 0 ? void 0 : _a.findDirectExpression(Expressions.SQLCond);
if (where === undefined) {
return undefined;
}
let into = high.findFirstExpression(Expressions.SQLIntoList);
if (into === undefined) {
into = high.findFirstExpression(Expressions.SQLIntoStructure);
}
if (into === undefined) {
into = high.findFirstExpression(Expressions.SQLIntoTable);
}
if (into === undefined) {
return undefined;
}
if (where.getLastToken().getEnd().isBefore(into.getFirstToken().getStart()) === false) {
return undefined;
}
const from = high.findFirstExpression(Expressions.SQLFrom);
if (from === undefined) {
return undefined;
}
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, from.getLastToken().getEnd(), ` ` + into.concatTokens());
const fix2 = edit_helper_1.EditHelper.deleteRange(lowFile, into.getFirstToken().getStart(), into.getLastToken().getEnd());
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, low.getFirstToken(), "SQL, move INTO", this.getMetadata().key, this.conf.severity, fix);
}
/** removes @'s and commas */
downportSQLExtras(low, high, lowFile, highSyntax) {
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
if (!(high.get() instanceof Statements.Select)
&& !(high.get() instanceof Statements.SelectLoop)
&& !(high.get() instanceof Statements.UpdateDatabase)
&& !(high.get() instanceof Statements.ModifyDatabase)
&& !(high.get() instanceof Statements.DeleteDatabase)
&& !(high.get() instanceof Statements.InsertDatabase)) {
return undefined;
}
let fix = undefined;
const addFix = (token) => {
const add = edit_helper_1.EditHelper.deleteToken(lowFile, token);
if (fix === undefined) {
fix = add;
}
else {
fix = edit_helper_1.EditHelper.merge(fix, add);
}
};
const candidates = [high.findAllExpressionsRecursive(Expressions.SQLTarget),
high.findAllExpressionsRecursive(Expressions.SQLSource),
high.findAllExpressionsRecursive(Expressions.SQLSourceNoSpace),
high.findAllExpressionsRecursive(Expressions.SQLSourceSimple)].flat();
for (const c of candidates.reverse()) {
if (c.getFirstToken() instanceof tokens_1.WAt
|| c.getFirstToken() instanceof tokens_1.At) {
const tokens = c.getAllTokens();
if (tokens[1] instanceof tokens_1.ParenLeftW && tokens[tokens.length - 1] instanceof tokens_1.WParenRightW) {
const source = c.findDirectExpression(Expressions.Source);
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, high.getStart(), `DATA(${uniqueName}) = ${source === null || source === void 0 ? void 0 : source.concatTokens()}.\n`);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, c.getFirstToken().getStart(), c.getLastToken().getEnd(), "@" + uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, low.getFirstToken(), "SQL, outline complex @", this.getMetadata().key, this.conf.severity, fix);
}
else {
addFix(c.getFirstToken());
}
}
}
for (const fieldList of high.findAllExpressionsMulti([Expressions.SQLFieldList, Expressions.SQLFieldListLoop, Expressions.SQLOrderBy], true)) {
for (const token of fieldList.getDirectTokens()) {
if (token.getStr() === ",") {
addFix(token);
}
}
}
if (fix !== undefined) {
return issue_1.Issue.atToken(lowFile, low.getFirstToken(), "SQL, remove @ and ,", this.getMetadata().key, this.conf.severity, fix);
}
for (const c of high.findAllExpressionsRecursive(Expressions.SQLIn)) {
const children = c.getChildren();
const first = children[1];
if (!(first.get() instanceof tokens_1.WParenLeftW)) {
continue;
}
const last = children[children.length - 1];
if (last.get() instanceof tokens_1.WParenRightW || last.get() instanceof tokens_1.WParenRight) {
const firstEnd = first.getFirstToken().getEnd();
const endDelete = new position_1.Position(firstEnd.getRow(), firstEnd.getCol() + 1);
const fix1 = edit_helper_1.EditHelper.deleteRange(lowFile, firstEnd, endDelete);
const lastStart = last.getFirstToken().getStart();
const startDelete = new position_1.Position(lastStart.getRow(), lastStart.getCol() - 1);
const fix2 = edit_helper_1.EditHelper.deleteRange(lowFile, startDelete, lastStart);
fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, low.getFirstToken(), "SQL, remove spaces", this.getMetadata().key, this.conf.severity, fix);
}
}
return undefined;
}
downportSelectExistence(low, high, lowFile, highSyntax) {
var _a, _b, _c, _d;
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
else if (!(high.get() instanceof Statements.Select)) {
return undefined;
}
const fieldList = high.findFirstExpression(Expressions.SQLFieldList);
if ((fieldList === null || fieldList === void 0 ? void 0 : fieldList.concatTokens().toUpperCase()) !== "@ABAP_TRUE") {
return undefined;
}
const fieldName = (_b = (_a = high.findFirstExpression(Expressions.SQLCond)) === null || _a === void 0 ? void 0 : _a.findFirstExpression(Expressions.SQLFieldName)) === null || _b === void 0 ? void 0 : _b.concatTokens();
if (fieldName === undefined) {
return undefined;
}
const into = high.findFirstExpression(Expressions.SQLIntoStructure);
if (into === undefined) {
return undefined;
}
const intoName = (_d = (_c = into.findFirstExpression(Expressions.SQLTarget)) === null || _c === void 0 ? void 0 : _c.findFirstExpression(Expressions.Target)) === null || _d === void 0 ? void 0 : _d.concatTokens();
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const fix1 = edit_helper_1.EditHelper.replaceRange(lowFile, fieldList.getFirstToken().getStart(), fieldList.getLastToken().getEnd(), fieldName);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, into === null || into === void 0 ? void 0 : into.getFirstToken().getStart(), into === null || into === void 0 ? void 0 : into.getLastToken().getEnd(), `INTO @DATA(${uniqueName})`);
let fix = edit_helper_1.EditHelper.merge(fix2, fix1);
const fix3 = edit_helper_1.EditHelper.insertAt(lowFile, high.getLastToken().getEnd(), `\nCLEAR ${intoName}.\nIF sy-subrc = 0.\n ${intoName} = abap_true.\nENDIF.`);
fix = edit_helper_1.EditHelper.merge(fix, fix3);
return issue_1.Issue.atToken(lowFile, low.getFirstToken(), "SQL, refactor existence check", this.getMetadata().key, this.conf.severity, fix);
}
downportSelectInline(low, high, lowFile, highSyntax) {
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
else if (!(high.get() instanceof Statements.Select) && !(high.get() instanceof Statements.SelectLoop)) {
return undefined;
}
// as first step outline the @DATA, note that void types are okay, as long the field names are specified
let found = this.downportSelectSingleInline(low, high, lowFile, highSyntax);
if (found) {
return found;
}
found = this.downportSelectTableInline(low, high, lowFile, highSyntax);
if (found) {
return found;
}
return undefined;
}
downportSelectFields(low, high, lowFile, _highSyntax) {
var _a;
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
else if (!(high.get() instanceof Statements.Select)) {
return undefined;
}
const fields = high.findFirstExpression(Expressions.SQLFields);
if (fields === undefined) {
return undefined;
}
const code = (_a = fields.getLastChild()) === null || _a === void 0 ? void 0 : _a.concatTokens();
if (code === undefined) {
return undefined;
}
const fix1 = edit_helper_1.EditHelper.deleteRange(lowFile, fields.getFirstToken().getStart(), fields.getLastToken().getEnd());
const fix2 = edit_helper_1.EditHelper.insertAt(lowFile, high.getFirstToken().getEnd(), " " + code);
const fix = edit_helper_1.EditHelper.merge(fix1, fix2);
return issue_1.Issue.atToken(lowFile, fields.getFirstToken(), "Replace FIELDS", this.getMetadata().key, this.conf.severity, fix);
}
downportSelectSingleInline(low, high, lowFile, _highSyntax) {
var _a, _b, _c, _d;
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
const targets = ((_a = high.findFirstExpression(Expressions.SQLIntoStructure)) === null || _a === void 0 ? void 0 : _a.findDirectExpressions(Expressions.SQLTarget)) || [];
if (targets.length !== 1) {
return undefined;
}
const inlineData = targets[0].findFirstExpression(Expressions.InlineData);
if (inlineData === undefined) {
return undefined;
}
const sqlFrom = high.findAllExpressions(Expressions.SQLFromSource);
if (sqlFrom.length !== 1) {
return undefined;
}
const tableName = (_b = sqlFrom[0].findDirectExpression(Expressions.DatabaseTable)) === null || _b === void 0 ? void 0 : _b.concatTokens();
if (tableName === undefined) {
return undefined;
}
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
let fieldList = high.findFirstExpression(Expressions.SQLFieldList);
if (fieldList === undefined) {
fieldList = high.findFirstExpression(Expressions.SQLFieldListLoop);
}
if (fieldList === undefined) {
return undefined;
}
let fieldDefinition = "";
const fields = fieldList.findAllExpressions(Expressions.SQLFieldName);
const name = ((_c = inlineData.findFirstExpression(Expressions.TargetField)) === null || _c === void 0 ? void 0 : _c.concatTokens()) || "error";
if (fields.length === 1) {
fieldDefinition = `DATA ${name} TYPE ${tableName}-${fields[0].concatTokens()}.`;
}
else if (fieldList.concatTokens() === "*") {
fieldDefinition = `DATA ${name} TYPE ${tableName}.`;
}
else if (fieldList.concatTokens().toUpperCase() === "COUNT( * )") {
fieldDefinition = `DATA ${name} TYPE i.`;
}
else if (fieldList.concatTokens().toUpperCase() === "@ABAP_TRUE"
|| fieldList.concatTokens().toUpperCase() === "@ABAP_FALSE") {
fieldDefinition = `DATA ${name} TYPE abap_bool.`;
}
else if (fieldList.getChildren().length === 1 && fieldList.getChildren()[0].get() instanceof Expressions.SQLAggregation) {
const c = fieldList.getChildren()[0];
if (c instanceof nodes_1.ExpressionNode) {
const concat = (_d = c.findFirstExpression(Expressions.SQLArithmetics)) === null || _d === void 0 ? void 0 : _d.concatTokens();
fieldDefinition = `DATA ${name} TYPE ${tableName}-${concat}.`;
}
}
else {
for (const f of fields) {
const fieldName = f.concatTokens();
fieldDefinition += indentation + " " + fieldName + " TYPE " + tableName + "-" + fieldName + ",\n";
}
fieldDefinition = `DATA: BEGIN OF ${name},
${fieldDefinition}${indentation} END OF ${name}.`;
}
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, high.getStart(), `${fieldDefinition}
${indentation}`);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, inlineData.getFirstToken().getStart(), inlineData.getLastToken().getEnd(), name);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, inlineData.getFirstToken(), "Outline SELECT @DATA", this.getMetadata().key, this.conf.severity, fix);
}
downportSelectTableInline(low, high, lowFile, highSyntax) {
var _a, _b, _c, _d, _e, _f, _g;
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
const targets = ((_a = high.findFirstExpression(Expressions.SQLIntoTable)) === null || _a === void 0 ? void 0 : _a.findDirectExpressions(Expressions.SQLTarget)) || [];
if (targets.length !== 1) {
return undefined;
}
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
const inlineData = targets[0].findFirstExpression(Expressions.InlineData);
if (inlineData === undefined) {
return undefined;
}
const sqlFrom = high.findAllExpressions(Expressions.SQLFromSource);
if (sqlFrom.length === 0) {
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Error outlining, sqlFrom not found", this.getMetadata().key, this.conf.severity);
}
let tableName = (_b = sqlFrom[0].findDirectExpression(Expressions.DatabaseTable)) === null || _b === void 0 ? void 0 : _b.concatTokens();
if (tableName === undefined) {
return undefined;
}
const tableMap = {};
for (const from of sqlFrom) {
const dbName = (_c = from.findDirectExpression(Expressions.DatabaseTable)) === null || _c === void 0 ? void 0 : _c.concatTokens().toUpperCase();
if (dbName === undefined) {
continue;
}
const asName = ((_d = from.findDirectExpression(Expressions.SQLAsName)) === null || _d === void 0 ? void 0 : _d.concatTokens().toUpperCase()) || dbName;
tableMap[asName] = dbName;
}
const fieldList = high.findFirstExpression(Expressions.SQLFieldList);
if (fieldList === undefined) {
return undefined;
}
let fieldDefinitions = "";
for (const f of fieldList.findAllExpressions(Expressions.SQLField)) {
let fieldName = (_e = f.findFirstExpression(Expressions.SQLFieldName)) === null || _e === void 0 ? void 0 : _e.concatTokens();
if (fieldName === undefined) {
continue;
}
if (fieldName.includes("~")) {
const split = fieldName.split("~");
tableName = split[0];
fieldName = split[1];
}
const translated = tableMap[tableName.toUpperCase()];
const typeName = translated + "-" + fieldName;
fieldName = ((_f = f.findFirstExpression(Expressions.SQLAsName)) === null || _f === void 0 ? void 0 : _f.concatTokens()) || fieldName;
fieldDefinitions += indentation + " " + fieldName + " TYPE " + typeName.toLowerCase() + ",\n";
}
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const name = ((_g = inlineData.findFirstExpression(Expressions.TargetField)) === null || _g === void 0 ? void 0 : _g.concatTokens()) || "error";
let fix1 = edit_helper_1.EditHelper.insertAt(lowFile, high.getStart(), `TYPES: BEGIN OF ${uniqueName},
${fieldDefinitions}${indentation} END OF ${uniqueName}.
${indentation}DATA ${name} TYPE STANDARD TABLE OF ${uniqueName} WITH DEFAULT KEY.
${indentation}`);
if (fieldDefinitions === "") {
fix1 = edit_helper_1.EditHelper.insertAt(lowFile, high.getStart(), `DATA ${name} TYPE STANDARD TABLE OF ${tableName} WITH DEFAULT KEY.
${indentation}`);
}
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, inlineData.getFirstToken().getStart(), inlineData.getLastToken().getEnd(), name);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, inlineData.getFirstToken(), "Outline SELECT @DATA", this.getMetadata().key, this.conf.severity, fix);
}
// the anonymous type might be used in inferred type statements, define it so it can be referred
anonymousTableType(high, lowFile, highSyntax) {
if (!(high.get() instanceof Statements.Data)) {
return undefined;
}
const tt = high.findFirstExpression(Expressions.TypeTable);
if (tt === undefined) {
return undefined;
}
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const code = `TYPES ${uniqueName} ${tt.concatTokens()}.\n`;
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, high.getStart(), code);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, tt.getFirstToken().getStart(), tt.getLastToken().getEnd(), "TYPE " + uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Add type for table definition", this.getMetadata().key, this.conf.severity, fix);
}
downportMessage(high, lowFile, highSyntax) {
var _a, _b;
if (!(high.get() instanceof Statements.Message)) {
return undefined;
}
const foundWith = high.findExpressionAfterToken("WITH");
if (foundWith === undefined) {
return undefined;
}
const likeSource = high.findExpressionAfterToken("LIKE");
for (const s of high.findAllExpressions(Expressions.Source)) {
if (s === likeSource) {
continue;
}
else if (s.getChildren().length === 1 && ((_a = s.getFirstChild()) === null || _a === void 0 ? void 0 : _a.get()) instanceof Expressions.Constant) {
continue;
}
else if (s.getChildren().length === 1 && ((_b = s.getFirstChild()) === null || _b === void 0 ? void 0 : _b.get()) instanceof Expressions.FieldChain) {
continue;
}
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
const firstToken = high.getFirstToken();
const code = `DATA(${uniqueName}) = ${s.concatTokens()}.\n${indentation}`;
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, firstToken.getStart(), code);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, s.getFirstToken().getStart(), s.getLastToken().getEnd(), uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Refactor MESSAGE WITH source", this.getMetadata().key, this.conf.severity, fix);
}
return undefined;
}
replaceAppendExpression(high, lowFile, highSyntax) {
if (!(high.get() instanceof Statements.Append)) {
return undefined;
}
const children = high.getChildren();
if (children[1].get() instanceof Expressions.Source) {
const source = children[1];
const target = high.findDirectExpression(Expressions.Target);
if (target === undefined) {
return undefined;
}
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
const firstToken = high.getFirstToken();
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, firstToken.getStart(), `DATA ${uniqueName} LIKE LINE OF ${target === null || target === void 0 ? void 0 : target.concatTokens()}.
${indentation}${uniqueName} = ${source.concatTokens()}.\n${indentation}`);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, source.getFirstToken().getStart(), source.getLastToken().getEnd(), uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Outline APPEND source expression", this.getMetadata().key, this.conf.severity, fix);
}
return undefined;
}
downportReadTable(high, lowFile, highSyntax) {
if (!(high.get() instanceof Statements.ReadTable)) {
return undefined;
}
const source = high.findExpressionAfterToken("TABLE");
if ((source === null || source === void 0 ? void 0 : source.get()) instanceof Expressions.Source) {
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
const firstToken = high.getFirstToken();
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, firstToken.getStart(), `DATA(${uniqueName}) = ${source.concatTokens()}.\n` + indentation);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, source.getFirstToken().getStart(), source.getLastToken().getEnd(), uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Outline table source", this.getMetadata().key, this.conf.severity, fix);
}
return undefined;
}
downportMessageSource(high, lowFile, highSyntax) {
var _a;
if (!(high.get() instanceof Statements.Message)) {
return undefined;
}
const source = high.findExpressionAfterToken("MESSAGE");
if ((source === null || source === void 0 ? void 0 : source.get()) instanceof Expressions.MessageSourceSource
&& ((_a = source.getFirstChild()) === null || _a === void 0 ? void 0 : _a.get()) instanceof Expressions.Source) {
;
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
const firstToken = high.getFirstToken();
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, firstToken.getStart(), `DATA(${uniqueName}) = ${source.concatTokens()}.\n` + indentation);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, source.getFirstToken().getStart(), source.getLastToken().getEnd(), uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Outline message source", this.getMetadata().key, this.conf.severity, fix);
}
return undefined;
}
replaceInsertExpression(high, lowFile, highSyntax) {
if (!(high.get() instanceof Statements.InsertInternal)) {
return undefined;
}
const children = high.getChildren();
if (children[1].get() instanceof Expressions.Source) {
const source = children[1];
const target = high.findDirectExpression(Expressions.Target);
if (target === undefined) {
return undefined;
}
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
const firstToken = high.getFirstToken();
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, firstToken.getStart(), `DATA ${uniqueName} LIKE LINE OF ${target === null || target === void 0 ? void 0 : target.concatTokens()}.
${indentation}${uniqueName} = ${source.concatTokens()}.\n${indentation}`);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, source.getFirstToken().getStart(), source.getLastToken().getEnd(), uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Outline INSERT source expression", this.getMetadata().key, this.conf.severity, fix);
}
return undefined;
}
replaceTableExpression(low, high, lowFile, highSyntax) {
if (!(low.get() instanceof _statement_1.Unknown)) {
return undefined;
}
for (const fieldChain of high.findAllExpressionsRecursive(Expressions.FieldChain)) {
const tableExpression = fieldChain.findDirectExpression(Expressions.TableExpression);
if (tableExpression === undefined) {
continue;
}
const concat = high.concatTokens().toUpperCase();
if (concat.includes(" LINE_EXISTS( ") || concat.includes(" LINE_INDEX( ")) {
// note: line_exists() must be replaced before handling table expressions
continue;
}
let pre = "";
let startToken = undefined;
for (const child of fieldChain.getChildren()) {
if (startToken === undefined) {
startToken = child.getFirstToken();
}
else if (child === tableExpression) {
break;
}
pre += child.concatTokens();
}
if (startToken === undefined) {
continue;
}
const condition = this.tableCondition(tableExpression);
const uniqueName = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const tabixBackup = this.uniqueName(high.getFirstToken().getStart(), lowFile.getFilename(), highSyntax);
const indentation = " ".repeat(high.getFirstToken().getStart().getCol() - 1);
const firstToken = high.getFirstToken();
// note that the tabix restore should be done before throwing the exception
const fix1 = edit_helper_1.EditHelper.insertAt(lowFile, firstToken.getStart(), `DATA ${uniqueName} LIKE LINE OF ${pre}.
${indentation}DATA ${tabixBackup} LIKE sy-tabix.
${indentation}${tabixBackup} = sy-tabix.
${indentation}READ TABLE ${pre} ${condition}INTO ${uniqueName}.
${indentation}sy-tabix = ${tabixBackup}.
${indentation}IF sy-subrc <> 0.
${indentation} RAISE EXCEPTION TYPE cx_sy_itab_line_not_found.
${indentation}ENDIF.
${indentation}`);
const fix2 = edit_helper_1.EditHelper.replaceRange(lowFile, startToken.getStart(), tableExpression.getLastToken().getEnd(), uniqueName);
const fix = edit_helper_1.EditHelper.merge(fix2, fix1);
if (high.get() instanceof Statements.ElseIf) {
throw "downport, unable to downport table expression in ELSEIF";
}
return issue_1.Issue.atToken(lowFile, high.getFirstToken(), "Outline table expression", this.getMetadata().key, this.conf.severity, fix);
}
return undefined;
}
tableCondition(tableExpression) {
let condition = "";
let keyName = "";
for (const c of tableExpression.getChildren() || []) {
if (c.getFirstToken().getStr() === "[" || c.getFirstToken().getStr() === "]") {
continue;
}
else if (c.get() instanceof Expressions.ComponentChainSimple && condition === "") {
if (keyName === "") {
condition = "WITH KEY ";
}
else {
condition = "WITH TABLE KEY " + keyName + " COMPONENTS ";
}
}
else if (c.get() instanceof Expressions.Source && condition === "") {
condition = "INDEX ";
}
else if (c instanceof nodes_1.TokenNode && c.getFirstToken().getStr().toUpperCase() === "KEY") {
continue;
}
else if (c.get() instanceof Expressions.SimpleName) {
keyName = c.concatTokens();
continue;
}
condition += c.concatTokens() + " ";
}
return condition;
}