private _validateDecorator()

in tools/tslint-rules/validateDecoratorsRule.ts [103:194]


  private _validateDecorator(decorator: ts.Decorator) {
    const expression = decorator.expression;

    if (!expression || !ts.isCallExpression(expression)) {
      return;
    }

    // Get the rules that are relevant for the current decorator.
    const rules = this._rules[expression.expression.getText()];
    const args = expression.arguments;

    // Don't do anything if there are no rules.
    if (!rules) {
      return;
    }

    const allPropsRequirement = rules.requiredProps[ALL_PROPS_TOKEN];

    // If we have a rule that applies to all properties, we just run it through once and we exit.
    if (allPropsRequirement) {
      const argumentText = args[rules.argument] ? args[rules.argument].getText() : '';
      if (!allPropsRequirement.test(argumentText)) {
        this.addFailureAtNode(
          expression.parent,
          `Expected decorator argument ${rules.argument} ` + `to match "${allPropsRequirement}"`,
        );
      }
      return;
    }

    if (!args[rules.argument]) {
      if (rules.required) {
        this.addFailureAtNode(
          expression.parent,
          `Missing required argument at index ${rules.argument}`,
        );
      }
      return;
    }

    if (!ts.isObjectLiteralExpression(args[rules.argument])) {
      return;
    }

    // Extract the property names and values.
    const props: {name: string; value: string; node: ts.PropertyAssignment}[] = [];

    (args[rules.argument] as ts.ObjectLiteralExpression).properties.forEach(prop => {
      if (ts.isPropertyAssignment(prop) && prop.name && prop.initializer) {
        props.push({
          name: prop.name.getText(),
          value: prop.initializer.getText(),
          node: prop,
        });
      }
    });

    // Find all of the required rule properties that are missing from the decorator.
    const missing = Object.keys(rules.requiredProps).filter(
      key => !props.find(prop => prop.name === key),
    );

    if (missing.length) {
      // Exit early if any of the properties are missing.
      this.addFailureAtNode(
        expression.expression,
        'Missing required properties: ' + missing.join(', '),
      );
    } else {
      // If all the necessary properties are defined, ensure that
      // they match the pattern and aren't in the forbidden list.
      props
        .filter(prop => rules.requiredProps[prop.name] || rules.forbiddenProps[prop.name])
        .forEach(prop => {
          const {name, value, node} = prop;
          const requiredPattern = rules.requiredProps[name];
          const forbiddenPattern = rules.forbiddenProps[name];

          if (requiredPattern && !requiredPattern.test(value)) {
            this.addFailureAtNode(
              node,
              `Invalid value for property. ` + `Expected value to match "${requiredPattern}".`,
            );
          } else if (forbiddenPattern && forbiddenPattern.test(value)) {
            this.addFailureAtNode(
              node,
              `Property value not allowed. ` + `Value should not match "${forbiddenPattern}".`,
            );
          }
        });
    }
  }