@abaplint/core
Version:
abaplint - Core API
265 lines • 11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeywordCase = exports.KeywordCaseConf = exports.KeywordCaseStyle = void 0;
const issue_1 = require("../issue");
const _abap_rule_1 = require("./_abap_rule");
const nodes_1 = require("../abap/nodes");
const _statement_1 = require("../abap/2_statements/statements/_statement");
const tokens_1 = require("../abap/1_lexer/tokens");
const objects_1 = require("../objects");
const _basic_rule_config_1 = require("./_basic_rule_config");
const Statements = require("../abap/2_statements/statements");
const Expressions = require("../abap/2_statements/expressions");
const _irule_1 = require("./_irule");
const ddic_1 = require("../ddic");
const virtual_position_1 = require("../virtual_position");
const edit_helper_1 = require("../edit_helper");
var KeywordCaseStyle;
(function (KeywordCaseStyle) {
KeywordCaseStyle["Upper"] = "upper";
KeywordCaseStyle["Lower"] = "lower";
})(KeywordCaseStyle || (exports.KeywordCaseStyle = KeywordCaseStyle = {}));
class KeywordCaseConf extends _basic_rule_config_1.BasicRuleConfig {
constructor() {
super(...arguments);
this.style = KeywordCaseStyle.Upper;
/** Ignore global exception classes */
this.ignoreExceptions = true;
this.ignoreLowerClassImplmentationStatement = true;
this.ignoreGlobalClassDefinition = false;
this.ignoreGlobalInterface = false;
this.ignoreFunctionModuleName = false;
// this ignores keywords in CLASS/ENDCLASS statements of a global class (and only in them, the rest is checked)
this.ignoreGlobalClassBoundaries = false;
/** A list of keywords to be ignored */
this.ignoreKeywords = [];
}
}
exports.KeywordCaseConf = KeywordCaseConf;
class Skip {
constructor(conf) {
this.skip = false;
this.isGlobalClass = false;
this.isGlobalIf = false;
this.conf = conf;
}
skipStatement(statement) {
const get = statement.get();
if (get instanceof _statement_1.Unknown
|| get instanceof _statement_1.NativeSQL
|| get instanceof _statement_1.MacroContent
|| get instanceof _statement_1.MacroCall
|| statement.getFirstToken().getStart() instanceof virtual_position_1.VirtualPosition
|| get instanceof _statement_1.Comment) {
return true;
}
if (this.conf.ignoreGlobalClassBoundaries) {
const node = get;
if (node instanceof Statements.Interface && statement.findFirstExpression(Expressions.ClassGlobal)) {
this.isGlobalIf = true;
return true;
}
else if (this.isGlobalIf === true && node instanceof Statements.EndInterface) {
return true;
}
if (node instanceof Statements.ClassDefinition && statement.findFirstExpression(Expressions.ClassGlobal)) {
this.isGlobalClass = true;
return true;
}
else if (this.isGlobalClass === true
&& (node instanceof Statements.EndClass || node instanceof Statements.ClassImplementation)) {
return true;
}
}
if (this.conf.ignoreGlobalClassDefinition) {
if (get instanceof Statements.ClassDefinition
&& statement.findFirstExpression(Expressions.ClassGlobal)) {
this.skip = true;
return true;
}
else if (this.skip === true && get instanceof Statements.EndClass) {
this.skip = false;
return true;
}
else if (this.skip === true) {
return true;
}
}
if (this.conf.ignoreGlobalInterface) {
if (get instanceof Statements.Interface
&& statement.findFirstExpression(Expressions.ClassGlobal)) {
this.skip = true;
return true;
}
else if (this.skip === true && get instanceof Statements.EndInterface) {
this.skip = false;
return true;
}
else if (this.skip === true) {
return true;
}
}
return false;
}
}
class KeywordCase extends _abap_rule_1.ABAPRule {
constructor() {
super(...arguments);
this.conf = new KeywordCaseConf();
}
getMetadata() {
return {
key: "keyword_case",
title: "Keyword case",
shortDescription: `Checks that keywords have the same case. Non-keywords must be lower case.`,
extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#use-your-pretty-printer-team-settings`,
tags: [_irule_1.RuleTag.Styleguide, _irule_1.RuleTag.SingleFile, _irule_1.RuleTag.Quickfix],
badExample: `write 'hello world'.`,
goodExample: `WRITE 'hello world'.`,
};
}
getConfig() {
return this.conf;
}
setConfig(conf) {
this.conf = conf;
if (this.conf === undefined) {
this.conf = new KeywordCaseConf();
}
if (this.conf.style === undefined) {
this.conf = new KeywordCaseConf();
}
if (this.conf.ignoreExceptions === undefined) {
this.conf.ignoreExceptions = new KeywordCaseConf().ignoreExceptions;
}
}
runParsed(file, obj) {
const issues = [];
const ddic = new ddic_1.DDIC(this.reg);
const MAX_ISSUES = 100;
if (this.conf.ignoreExceptions && obj instanceof objects_1.Class) {
const definition = obj.getClassDefinition();
if (definition === undefined || ddic.isException(definition, obj)) {
return [];
}
}
const skip = new Skip(this.getConfig());
let prev = undefined;
for (const statement of file.getStatements()) {
if (skip.skipStatement(statement) === true) {
continue;
}
let result = this.traverse(statement, statement.get());
if (result.length > 0) {
if (prev && result[0].token.getStart().equals(prev.getStart())) {
continue;
}
if (statement.getColon() !== undefined) {
// if its a chained statement, go token by token
result = [result[0]];
}
const issue = this.build(result, file);
issues.push(issue);
if (issues.length > MAX_ISSUES) {
break;
}
prev = result[0].token;
}
}
return issues;
}
//////////////////
build(tokens, file) {
const first = tokens[0];
const firstToken = tokens[0].token;
const lastToken = tokens[tokens.length - 1].token;
const firstTokenValue = firstToken.getStr();
let description = "";
if (first.keyword === true) {
description = `Keyword should be ${this.conf.style} case: "${firstTokenValue}"`;
}
else {
description = `Identifiers should be lower case: "${firstTokenValue}"`;
}
const draft = new edit_helper_1.EditDraft(file);
for (const token of tokens) {
const str = token.token.getStr();
const pos = token.token.getStart();
if (token.keyword === true) {
if (this.conf.style === KeywordCaseStyle.Lower) {
draft.replace(pos, str.toLowerCase());
}
else {
draft.replace(pos, str.toUpperCase());
}
}
else {
draft.replace(pos, str.toLowerCase());
}
}
const fix = draft.toEdit();
return issue_1.Issue.atRange(file, firstToken.getStart(), lastToken.getEnd(), description, this.getMetadata().key, this.conf.severity, fix);
}
/** returns a list of tokens which violates the keyword_case rule */
traverse(s, parent) {
let ret = [];
const children = s.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child instanceof nodes_1.TokenNodeRegex) {
const next = children[i + 1];
if (this.conf.ignoreLowerClassImplmentationStatement
&& parent instanceof Statements.ClassImplementation) {
continue;
}
const str = child.get().getStr();
const upper = str.toUpperCase();
// todo, this is a hack, the parser should recongize OTHERS/TEXT as a keyword
if (upper === "OTHERS" || (upper === "TEXT" && (next === null || next === void 0 ? void 0 : next.concatTokens()) === "-")) {
continue;
}
if (this.conf.ignoreFunctionModuleName === true
&& parent instanceof Statements.FunctionModule && upper !== "FUNCTION") {
continue;
}
// todo, this is a hack, the parser should recigize SCREEN as a keyword
if (upper === "SCREEN"
&& (parent instanceof Statements.ModifyDatabase
|| parent instanceof Statements.ModifyInternal
|| parent instanceof Statements.Loop)) {
continue;
}
if (str !== str.toLowerCase() && child.get() instanceof tokens_1.Identifier) {
ret.push({ token: child.get(), keyword: false });
}
}
else if (child instanceof nodes_1.TokenNode) {
const str = child.get().getStr();
if (this.violatesRule(str) && child.get() instanceof tokens_1.Identifier) {
ret.push({ token: child.get(), keyword: true });
}
}
else if (child instanceof nodes_1.ExpressionNode) {
ret = ret.concat(this.traverse(child, parent));
}
else {
throw new Error("keyword_case, traverseStatement, unexpected node type");
}
}
return ret;
}
violatesRule(keyword) {
if (this.conf.ignoreKeywords && this.conf.ignoreKeywords.map(k => { return k.toUpperCase(); }).includes(keyword.toUpperCase())) {
return false;
}
if (this.conf.style === KeywordCaseStyle.Lower) {
return keyword !== keyword.toLowerCase();
}
else if (this.conf.style === KeywordCaseStyle.Upper) {
return keyword !== keyword.toUpperCase();
}
return false;
}
}
exports.KeywordCase = KeywordCase;
//# sourceMappingURL=keyword_case.js.map