All files / src/rules no_exclamation_escape.ts

100% Statements 84/84
100% Branches 17/17
100% Functions 6/6
100% Lines 84/84

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 851x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 18x 18x 1x 1x 317x 317x 1x 272x 272x 1x 1x 2520x 2520x 3679x 3671x 3671x 3679x 2520x 1x 18x 18x 1x 1x 1x 1x 14683x 14683x 14683x 14683x 269834x 269834x 269834x 269834x 269834x 269834x 269834x 269834x 269834x 269834x 269834x 269834x 269834x 14683x 14683x 14120x 14120x 14683x 14683x 264x 264x 14683x 14683x 311x 311x 311x 7040x 5083x 5083x 18x 18x 13x 13x 5x 5x 5x 5083x 7040x 311x 311x 311x 14683x  
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {EditHelper} from "../edit_helper";
import {Identifier} from "../abap/1_lexer/tokens";
import {ArtifactsABAP} from "../abap/artifacts";
import {Combi} from "../abap/2_statements/combi";
 
let keywords: Set<string> | undefined = undefined;
 
/** every word of the statement and expression grammar, upper case */
function getKeywords(): Set<string> {
  if (keywords === undefined) {
    const list: string[] = [];
    for (const stat of ArtifactsABAP.getStatements()) {
      list.push(...Combi.listKeywords(stat.getMatcher()));
    }
    for (const expr of ArtifactsABAP.getExpressions()) {
      list.push(...Combi.listKeywords(new expr().getRunnable()));
    }
    keywords = new Set<string>();
    for (const words of list) {
      // a WordSequence lists eg. "PREFERRED PARAMETER" or "IS-INITIAL" as one entry
      for (const word of words.replace(/-/g, " ").split(" ")) {
        if (word !== "") {
          keywords.add(word.toUpperCase());
        }
      }
    }
  }
  return keywords;
}
 
export class NoExclamationEscapeConf extends BasicRuleConfig {
}
 
export class NoExclamationEscape extends ABAPRule {
  private conf = new NoExclamationEscapeConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "no_exclamation_escape",
      title: "No exclamation escape",
      shortDescription: `Detects and removes exclamation marks (!) used to escape identifiers`,
      extendedInformation: `Exclamation marks are not needed when the identifier is not a keyword, or could be avoided by renaming.
 
An identifier named like an ABAP keyword is allowed to be escaped, eg. a method parameter "!default",
which the system otherwise reads as the DEFAULT addition of the parameter before it.`,
      tags: [RuleTag.SingleFile, RuleTag.Quickfix],
      badExample: "methods CONVERT changing !CO_sdf type ref to ZCL_sdf optional.",
      goodExample: "methods CONVERT changing CO_sdf type ref to ZCL_sdf optional.",
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: NoExclamationEscapeConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile): Issue[] {
    const issues: Issue[] = [];
 
    for (const token of file.getTokens()) {
      if (token instanceof Identifier) {
        const str = token.getStr();
        if (str.startsWith("!")) {
          const replacement = str.substring(1);
          if (getKeywords().has(replacement.toUpperCase())) {
            continue;
          }
          const fix = EditHelper.replaceToken(file, token, replacement);
          issues.push(Issue.atToken(file, token, "Do not use exclamation mark to escape identifiers", this.getMetadata().key, this.conf.severity, fix));
        }
      }
    }
 
    return issues;
  }
}