All files noObjectMutationRule.ts

95% Statements 57/60
89.8% Branches 44/49
93.33% Functions 14/15
96.08% Lines 49/51
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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 1281x 1x   1x               2x 1x 1x 1x     2x     1x 1x   1x 2x 2x   1x   1x         1x                               1x         1x         2x 2x     1x   266x 20x 37x 110x     17x         266x 4x 7x     3x         266x 6x 12x 9x     3x         266x 5x 10x 6x     4x       266x     1x 32x 22x   10x         10x 5x     5x   1x  
import * as ts from "typescript";
import * as Lint from "tslint";
 
const OPTION_IGNORE_PREFIX = "ignore-prefix";
 
export interface Options {
  readonly ignorePrefix: string | string[] | undefined,
}
 
function parseOptions(options: any[]): Options { //tslint:disable-line
  let ignorePrefix: string | undefined;
  for (const o of options) {
    Eif (typeof o === "object" && o[OPTION_IGNORE_PREFIX] !== null) { //tslint:disable-line
      ignorePrefix = o[OPTION_IGNORE_PREFIX];
      break;
    }
  }
  return { ignorePrefix };
}
 
export class Rule extends Lint.Rules.AbstractRule {
  public static FAILURE_STRING = "Modifying properties of existing object not allowed.";
 
  public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
    const noObjectMutationWalker = new NoObjectMutationWalker(sourceFile, this.getOptions());
    return this.applyWithWalker(noObjectMutationWalker);
  }
}
 
const objPropAccessors: ReadonlyArray<ts.SyntaxKind> = [
  ts.SyntaxKind.ElementAccessExpression,
  ts.SyntaxKind.PropertyAccessExpression
];
 
const forbidObjPropOnLeftSideOf: ReadonlyArray<ts.SyntaxKind> = [
  ts.SyntaxKind.EqualsToken,
  ts.SyntaxKind.PlusEqualsToken,
  ts.SyntaxKind.MinusEqualsToken,
  ts.SyntaxKind.AsteriskEqualsToken,
  ts.SyntaxKind.AsteriskAsteriskEqualsToken,
  ts.SyntaxKind.SlashEqualsToken,
  ts.SyntaxKind.PercentEqualsToken,
  ts.SyntaxKind.LessThanLessThanEqualsToken,
  ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
  ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
  ts.SyntaxKind.AmpersandEqualsToken,
  ts.SyntaxKind.BarEqualsToken,
  ts.SyntaxKind.CaretEqualsToken
];
 
const forbidUnaryOps: ReadonlyArray<ts.SyntaxKind> = [
  ts.SyntaxKind.PlusPlusToken,
  ts.SyntaxKind.MinusMinusToken
];
 
class NoObjectMutationWalker extends Lint.RuleWalker {
 
  ignorePrefix: string | string[] | undefined;
 
  constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
    super(sourceFile, options);
    Object.assign(this, parseOptions(options.ruleArguments));
  }
 
  public visitNode(node: ts.Node): void {
    // No assignment with object.property on the left
    if (node && node.kind === ts.SyntaxKind.BinaryExpression) {
      const binExp = node as ts.BinaryExpression;
      if (objPropAccessors.some((k) => k === binExp.left.kind) &&
          forbidObjPropOnLeftSideOf.some((k) => k === binExp.operatorToken.kind) &&
          !this.isIgnored(binExp.getText(this.getSourceFile()))
         ) {
        this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));
      }
    }
 
    // No deleting object properties
    if (node && node.kind === ts.SyntaxKind.DeleteExpression) {
      const delExp = node as ts.DeleteExpression;
      if (objPropAccessors.some((k) => k === delExp.expression.kind) &&
          !this.isIgnored(delExp.expression.getText(this.getSourceFile()))
         ) {
        this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));
      }
    }
 
    // No prefix inc/dec
    if (node && node.kind === ts.SyntaxKind.PrefixUnaryExpression) {
      const preExp = node as ts.PrefixUnaryExpression;
      if (objPropAccessors.some((k) => k === preExp.operand.kind) &&
          forbidUnaryOps.some((o) => o === preExp.operator) &&
          !this.isIgnored(preExp.operand.getText(this.getSourceFile()))
         ) {
        this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));
      }
    }
 
    // No postfix inc/dec
    if (node && node.kind === ts.SyntaxKind.PostfixUnaryExpression) {
      const postExp = node as ts.PostfixUnaryExpression;
      if (objPropAccessors.some((k) => k === postExp.operand.kind) &&
          forbidUnaryOps.some((o) => o === postExp.operator) &&
          !this.isIgnored(postExp.getText(this.getSourceFile()))
         ) {
        this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));
      }
    }
 
    super.visitNode(node);
  }
 
  private isIgnored(text: string): boolean {
    if (!this.ignorePrefix) {
      return false;
    }
    Iif (Array.isArray(this.ignorePrefix)) {
      if (this.ignorePrefix.find((pfx) => text.indexOf(pfx) === 0)) {
        return true;
      }
    } else {
      if (text.indexOf(this.ignorePrefix) === 0) {
        return true;
      }
    }
    return false;
  }
}