All files / src/rules generic_regex.ts

100% Statements 84/84
95% Branches 19/20
100% Functions 7/7
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 29650x 29650x 29650x 29650x 29650x 29650x 29650x 1x 14690x 14690x 14690x 14690x 14690x 142777x 142777x 142777x 142777x 142777x 142777x 142777x 142777x 142777x 142777x 142777x 142777x 142777x 14690x 14690x 14121x 14121x 14690x 14690x 274x 274x 274x 14690x 14690x 283x 29x 29x 29x 1x 1x 283x 283x 283x 14690x 14690x 380x 380x 363x 363x 17x 380x 18x 18x 22x 22x 37x 37x 60x 60x 37x 37x 23x 23x 23x 23x 60x 2x 2x 60x 37x 22x 18x 17x 17x 14690x  
import {Issue} from "../issue";
import {IObject} from "../objects/_iobject";
import {IRegistry} from "../_iregistry";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
 
export class GenericRegexConf extends BasicRuleConfig {
  /** List of forbidden regular expressions, case insensitive, checked against each line of every file.
   * Patterns use JavaScript regex syntax without enclosing slashes or flags.
   * @uniqueItems true
   */
  public regexes: string[] = [];
}
 
export class GenericRegex implements IRule {
  private conf = new GenericRegexConf();
  private regexes: RegExp[] = [];
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "generic_regex",
      title: "Generic regex",
      shortDescription: "Report an issue for each match of a configured regular expression.",
      extendedInformation: `Checks each line of every object file, including comments, strings, and XML.
Patterns use JavaScript regular expression syntax without enclosing slashes or flags.
Matching is case insensitive and does not span multiple lines. Each non-overlapping match is reported.
Use the standard exclude configuration to skip files.
 
Example configuration: \`{"generic_regex": {"regexes": ["TODO", "BREAK-POINT"]}}\``,
      tags: [RuleTag.SingleFile],
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: GenericRegexConf): void {
    this.conf = Object.assign(new GenericRegexConf(), conf);
    this.conf.regexes = this.conf.regexes ?? [];
  }
 
  public initialize(_reg: IRegistry) {
    this.regexes = this.conf.regexes.map(pattern => {
      try {
        return new RegExp(pattern, "gi");
      } catch {
        throw new Error(`generic_regex: Invalid regular expression "${pattern}"`);
      }
    });
    return this;
  }
 
  public run(obj: IObject): Issue[] {
    const issues: Issue[] = [];
    if (this.regexes.length === 0) {
      return issues;
    }
 
    for (const file of obj.getFiles()) {
      const rows = file.getRawRows();
      for (let row = 0; row < rows.length; row++) {
        const text = rows[row].replace(/\r$/, "");
        for (const regex of this.regexes) {
          regex.lastIndex = 0;
          for (;;) {
            const match = regex.exec(text);
            if (match === null) {
              break;
            }
            const startCol = match.index + 1;
            const message = `Text matches forbidden regular expression "${regex.source}"`;
            issues.push(Issue.atRowRange(file, row + 1, startCol, startCol + match[0].length,
                                         message, this.getMetadata().key, this.conf.severity));
            if (match[0].length === 0) {
              regex.lastIndex++;
            }
          }
        }
      }
    }
    return issues;
  }
}