Repository: prometheus/prometheus
File: web/ui/module/codemirror-promql/src/parser/parser.ts (TypeScript)

Code before:
      if (args.length > 1 && args[1].getChild(Identifier)) {
        this.addDiagnostic(node, `expected label selectors as the second argument to "info" function, got ${args[1].type}`);
      }
    }

    let j = 0;
    for (let i = 0; i < args.length; i++) {
      j = i;
      if (j >= funcSignature.argTypes.length) {
        if (funcSignature.variadic === 0) {
          // This is not a vararg function so we should not check the
          // type of the extra arguments.
          break;
        }
        j = funcSignature.argTypes.length - 1;
      }
      this.expectType(args[i], funcSignature.argTypes[j], `call to function "${funcSignature.name}"`);
    }
  }

  private checkSmoothedExpr(node: SyntaxNode): void {
    // A smoothed expression is supposed to be used in the following functions only:
    // rate, increase and delta.
    // So we need to check that the parent is a FunctionCall and that the function name is one of the above.
    const parent = node.parent?.parent;
    if (!parent || parent.type.id !== FunctionCall) {
      this.addDiagnostic(node, 'smoothed expression must be used inside a function call');
      return;
    }
    const funcID = parent.firstChild?.firstChild;
    if (!funcID) {
      this.addDiagnostic(node, 'function not defined');
      return;
    }
    const allowedFunctions = [Rate, Increase, Delta];
    if (!allowedFunctions.includes(funcID.type.id)) {
      this.addDiagnostic(node, 'smoothed expression can only be used in the following functions: rate, increase and delta');
    }
  }

  private checkAnchoredExpr(node: SyntaxNode): void {
    // An anchored expression is supposed to be used in the following functions only:
    // resets, changes, rate, increase and delta.
    // So we need to check that the parent is a FunctionCall and that the function name is one of the above.
    const parent = node.parent?.parent;
    if (!parent || parent.type.id !== FunctionCall) {
      this.addDiagnostic(node, 'anchored expression must be used inside a function call');
      return;
    }
    const funcID = parent.firstChild?.firstChild;
    if (!funcID) {

Review comment (suggestion):
Hmm what if there's no parent at all (should be ok), or if the parent is parentheses (doesn't make sense but should be ok), or if there's a future construct that allows modifying a range vector before feeding it into a function (like https://github.com/prometheus/prometheus/pull/14095)?

Maybe it is safer for this type of linting to only warn if the immediate parent (optionally after paren unwrapping) is a function, but one that isn't the right one?

Similar for the anchored checks.

Code after:
      if (args.length > 1 && args[1].getChild(Identifier)) {
        this.addDiagnostic(node, `expected label selectors as the second argument to "info" function, got ${args[1].type}`);
      }
    }

    let j = 0;
    for (let i = 0; i < args.length; i++) {
      j = i;
      if (j >= funcSignature.argTypes.length) {
        if (funcSignature.variadic === 0) {
          // This is not a vararg function so we should not check the
          // type of the extra arguments.
          break;
        }
        j = funcSignature.argTypes.length - 1;
      }
      this.expectType(args[i], funcSignature.argTypes[j], `call to function "${funcSignature.name}"`);
    }
  }

  private checkAnchoredSmoothedExpr(node: SyntaxNode, allowedFunctions: number[]): void {
    // A smoothed/anchored expression is supposed to work with range vectors or instant vectors.
    // So first thing to do is to check the type of the child.
    // Then, if this is used inside a function call, we need to check that the function is one of the given allowedFunctions.
    const nodeType = getType(node);
    if (nodeType !== ValueType.vector && nodeType !== ValueType.matrix) {
      this.addDiagnostic(node, `smoothed/anchored expression only allowed on instant vector or range vector selector, got ${nodeType} instead`);
      return;
    }
    const parent = node.parent?.parent;
    if (!parent || parent.type.id !== FunctionCall) {
      // Since the anchored/smoothed expression is not inside a function call, we cannot check the function name.
      // This is an acceptable case as the anchored/smoothed expression can be used on any vector expression.
      return;
    }
    const funcID = parent.firstChild?.firstChild;
    if (!funcID) {
      this.addDiagnostic(node, 'function not defined');
      return;
    }
    if (!allowedFunctions.includes(funcID.type.id)) {
      this.addDiagnostic(node, 'smoothed/anchored expression can only be used in specific functions');
    }
  }

  private checkVectorSelector(node: SyntaxNode): void {
    const matchList = node.getChild(LabelMatchers);
    const labelMatcherOpts = [QuotedLabelName, QuotedLabelMatcher, UnquotedLabelMatcher];
    let labelMatchers: Matcher[] = [];
    for (const labelMatcherOpt of labelMatcherOpts) {
      labelMatchers = labelMatchers.concat(buildLabelMatchers(matchList ? matchList.getChildren(labelMatcherOpt) : [], this.state));