@abaplint/core
Version:
abaplint - Core API
224 lines (218 loc) • 9.58 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreferInline = exports.PreferInlineConf = void 0;
const Statements = require("../abap/2_statements/statements");
const _basic_rule_config_1 = require("./_basic_rule_config");
const issue_1 = require("../issue");
const _irule_1 = require("./_irule");
const version_1 = require("../version");
const _abap_object_1 = require("../objects/_abap_object");
const syntax_1 = require("../abap/5_syntax/syntax");
const _scope_type_1 = require("../abap/5_syntax/_scope_type");
const _reference_1 = require("../abap/5_syntax/_reference");
const edit_helper_1 = require("../edit_helper");
class PreferInlineConf extends _basic_rule_config_1.BasicRuleConfig {
}
exports.PreferInlineConf = PreferInlineConf;
class PreferInline {
constructor() {
this.conf = new PreferInlineConf();
}
getMetadata() {
return {
key: "prefer_inline",
title: "Prefer Inline Declarations",
shortDescription: `Prefer inline to up-front declarations.`,
extendedInformation: `EXPERIMENTAL
Activates if language version is v740sp02 or above.
Variables must be local(METHOD or FORM).
No generic or void typed variables. No syntax errors.
First position used must be a full/pure write.
Move statment is not a cast(?=)
https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-inline-to-up-front-declarations`,
tags: [_irule_1.RuleTag.Styleguide, _irule_1.RuleTag.Upport, _irule_1.RuleTag.Experimental, _irule_1.RuleTag.Quickfix],
badExample: `DATA foo TYPE i.
foo = 2.
DATA percentage TYPE decfloat34.
percentage = ( comment_number / abs_statement_number ) * 100.`,
goodExample: `DATA(foo) = 2.
DATA(percentage) = CONV decfloat34( comment_number / abs_statement_number ) * 100.`,
};
}
getConfig() {
return this.conf;
}
initialize(reg) {
this.reg = reg;
return this;
}
setConfig(conf) {
this.conf = conf;
}
run(obj) {
if (obj.getType() === "INTF") {
return [];
}
if (this.reg.getConfig().getVersion() < version_1.Version.v740sp02 && this.reg.getConfig().getVersion() !== version_1.Version.Cloud) {
return [];
}
else if (!(obj instanceof _abap_object_1.ABAPObject)) {
return [];
}
const run = new syntax_1.SyntaxLogic(this.reg, obj).run();
if (run.issues.length > 0) {
return [];
}
const scopes = this.findScopeCandidates(run.spaghetti.getTop());
const ret = [];
for (const s of scopes) {
ret.push(...this.analyzeScope(s, obj));
}
return ret;
}
///////////////////////////
analyzeScope(node, obj) {
var _a, _b;
const ret = [];
const vars = node.getData().vars;
for (const name in vars) {
const identifier = vars[name];
if (this.isLocalDefinition(node, identifier) === false
|| identifier.getMeta().includes("inline" /* IdentifierMeta.InlineDefinition */)
|| identifier.getMeta().includes("form_parameter" /* IdentifierMeta.FormParameter */)) {
continue;
}
else if (identifier.getType().isGeneric() === true) {
continue;
}
else if (identifier.getType().containsVoid() === true) {
continue;
}
const write = this.firstUseIsWrite(node, identifier);
if (write === undefined) {
continue;
}
// check that it is a pure write, eg not sub component assignment
const next = this.findNextToken(write, obj);
if (next === undefined) {
continue;
}
else if ((next === null || next === void 0 ? void 0 : next.getStart().equals(write.position.getEnd())) && next.getStr() !== "." && next.getStr() !== ",") {
continue;
}
const file = obj.getABAPFileByName(identifier.getFilename());
const writeStatement = edit_helper_1.EditHelper.findStatement(next, file);
const statementType = writeStatement === null || writeStatement === void 0 ? void 0 : writeStatement.get();
if (statementType === undefined) {
continue;
}
// for now only allow some specific target statements, todo refactor
if (!(statementType instanceof Statements.Move
|| statementType instanceof Statements.Catch
|| statementType instanceof Statements.ReadTable
|| statementType instanceof Statements.Loop)
|| ((_a = writeStatement === null || writeStatement === void 0 ? void 0 : writeStatement.concatTokens()) === null || _a === void 0 ? void 0 : _a.includes("?="))
|| ((_b = writeStatement === null || writeStatement === void 0 ? void 0 : writeStatement.concatTokens()) === null || _b === void 0 ? void 0 : _b.includes(" #("))) {
continue;
}
const statement = edit_helper_1.EditHelper.findStatement(identifier.getToken(), file);
const concat = statement === null || statement === void 0 ? void 0 : statement.concatTokens().toUpperCase();
if (concat === null || concat === void 0 ? void 0 : concat.includes("BEGIN OF")) {
continue;
}
let fix = undefined;
if (file && statement) {
const fix1 = edit_helper_1.EditHelper.deleteStatement(file, statement);
const name = identifier.getName();
const replace = name.startsWith("<") ? "FIELD-SYMBOL(" + name + ")" : "DATA(" + name + ")";
const fix2 = edit_helper_1.EditHelper.replaceRange(file, write.position.getStart(), write.position.getEnd(), replace);
fix = edit_helper_1.EditHelper.merge(fix1, fix2);
}
const message = this.getMetadata().title + ", " + name;
ret.push(issue_1.Issue.atIdentifier(identifier, message, this.getMetadata().key, this.conf.severity, fix));
}
return ret;
}
////////////////////////
findNextToken(ref, obj) {
const file = obj.getABAPFileByName(ref.resolved.getFilename());
if (file === undefined) {
return undefined;
}
for (const t of file.getTokens()) {
if (t.getStart().isAfter(ref.position.getEnd())) {
return t;
}
}
return undefined;
}
firstUseIsWrite(node, identifier) {
// assumption: variables are local, so only the current scope must be searched
var _a, _b, _c;
for (const r of node.getData().references) {
if (r.referenceType === _reference_1.ReferenceType.TypeReference
&& ((_a = r.resolved) === null || _a === void 0 ? void 0 : _a.getStart().equals(identifier.getStart())) === true) {
return undefined;
}
}
let firstRead = undefined;
for (const r of node.getData().references) {
if (r.referenceType !== _reference_1.ReferenceType.DataReadReference
|| ((_b = r.resolved) === null || _b === void 0 ? void 0 : _b.getStart().equals(identifier.getStart())) === false) {
continue;
}
if (r.resolved) {
firstRead = { position: r.position, resolved: r.resolved };
break;
}
}
let firstWrite = undefined;
for (const w of node.getData().references) {
if (w.referenceType !== _reference_1.ReferenceType.DataWriteReference
|| ((_c = w.resolved) === null || _c === void 0 ? void 0 : _c.getStart().equals(identifier.getStart())) === false) {
continue;
}
if (w.resolved) {
firstWrite = { position: w.position, resolved: w.resolved };
break;
}
}
if (firstRead === undefined) {
return firstWrite;
}
else if (firstWrite === undefined) {
return undefined;
}
else if (firstWrite.position.getStart().getRow() === firstRead.position.getStart().getRow()) {
// if the same statement both reads and write the same variable
// note that currently just the line number is compared, this is not correct, it should check if its the same statement
return undefined;
}
else if (firstWrite.position.getStart().isBefore(firstRead.position.getStart())) {
return firstWrite;
}
return undefined;
}
isLocalDefinition(node, identifier) {
const { start, end } = node.calcCoverage();
if (identifier.getStart().isAfter(start) && identifier.getStart().isBefore(end)) {
return true;
}
else {
return false;
}
}
findScopeCandidates(node) {
if (node.getIdentifier().stype === _scope_type_1.ScopeType.Form
|| node.getIdentifier().stype === _scope_type_1.ScopeType.Method) {
return [node];
}
let ret = [];
for (const c of node.getChildren()) {
ret = ret.concat(this.findScopeCandidates(c));
}
return ret;
}
}
exports.PreferInline = PreferInline;
//# sourceMappingURL=prefer_inline.js.map