UNPKG

@abaplint/core

Version:
224 lines (218 loc) • 9.58 kB
"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