All files / src regex-rule.ts

100% Statements 45/45
100% Branches 28/28
100% Functions 4/4
100% Lines 44/44

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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 1056x     6x 6x   6x         30x   29x 28x 1x         27x 27x 1x 26x           1x       25x   25x 21x 4x 3x     25x 1x 24x 1x 1x       1x         12x 1x     11x   11x 4x 2x       7x 3x   3x 4x 2x       3x 1x               8x 2x 1x       6x 2x 2x 1x                      
import { Rule } from './rule';
import { Level, Node, RuleConfig } from './types';
 
const REGEX_FLAGS = '';
const VALID_KEYS = ['allow', 'disallow'];
 
export abstract class RegexRule extends Rule {
  protected allow?: RegExp | ReadonlyArray<RegExp>;
  protected disallow?: RegExp | ReadonlyArray<RegExp>;
 
  public constructor(config: Level | RuleConfig) {
    super(config);
 
    if (this.options) {
      if (!('allow' in this.options || 'disallow' in this.options)) {
        this.error(
          `Invalid option keys - must include one of ${VALID_KEYS.join(', ')}`
        );
      }
 
      for (const key in this.options) {
        if (VALID_KEYS.indexOf(key) < 0) {
          this.error(`Invalid key in options - ${key}`);
        } else if (
          !(
            typeof this.options[key] === 'string' ||
            Array.isArray(this.options[key])
          )
        ) {
          this.error(`Type of key ${key} must be a string or array of strings`);
        }
      }
 
      const { allow, disallow } = this.options;
 
      if (typeof allow === 'string') {
        this.allow = new RegExp(allow, REGEX_FLAGS);
      } else if (Array.isArray(allow)) {
        this.allow = allow.map((pattern) => new RegExp(pattern, REGEX_FLAGS));
      }
 
      if (typeof disallow === 'string') {
        this.disallow = new RegExp(disallow, REGEX_FLAGS);
      } else if (Array.isArray(disallow)) {
        this.disallow = disallow.map(
          (pattern) => new RegExp(pattern, REGEX_FLAGS)
        );
      }
    } else {
      this.error('Invalid options - must be an object');
    }
  }
 
  public run(node: Node) {
    if (!this.shouldRun(node)) {
      return;
    }
 
    const part = this.getPart(node);
 
    if (this.allow instanceof RegExp) {
      if (!this.allow.test(part)) {
        return this.report(
          `${node.path} does not match allowed pattern - ${this.allow}`
        );
      }
    } else if (Array.isArray(this.allow)) {
      let matchedAllowed = false;
 
      for (const allow of this.allow) {
        if (allow.test(part)) {
          matchedAllowed = true;
        }
      }
 
      if (!matchedAllowed) {
        return this.report(
          `${node.path} does not match any allowed patterns - ${this.allow.join(
            ', '
          )}`
        );
      }
    }
 
    if (this.disallow instanceof RegExp) {
      if (this.disallow.test(part)) {
        return this.report(
          `${node.path} matches disallowed pattern - ${this.disallow}`
        );
      }
    } else if (Array.isArray(this.disallow)) {
      for (const disallow of this.disallow) {
        if (disallow.test(part)) {
          return this.report(
            `${node.path} matches disallowed pattern - ${disallow}`
          );
        }
      }
    }
  }
 
  protected abstract getPart(node: Node): string;
  protected abstract shouldRun(node: Node): boolean;
}