All files / src/abap/5_syntax/expressions compare.ts

98% Statements 98/100
83.33% Branches 55/66
100% Functions 2/2
98% Lines 98/100

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 1011x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 272x 272x 272x 272x 465x 465x 272x 272x 1x 1x 272x 272x 22x 22x 272x 272x 272x 3x 3x 3x 269x 269x 272x 272x 272x 272x 272x 1x 1x 272x 268x 268x 3x 3x 3x 3x 3x 2x 3x 1x 1x 3x 3x 272x 1x 1x 1x 1x 272x 1x 1x 271x 272x 246x 246x 272x 272x 272x 272x 1x 1x 272x 17x 17x 7x 272x     272x 272x 272x 2x 2x 5x 5x 272x 1x 1x 272x 272x 2x 2x 2x 2x 1x 1x  
import * as Expressions from "../../2_statements/expressions";
import {ExpressionNode, TokenNode} from "../../nodes";
import {Source} from "./source";
import {MethodCallChain} from "./method_call_chain";
import {SourceFieldSymbol} from "./source_field_symbol";
import {SyntaxInput, syntaxIssue} from "../_syntax_input";
import {AbstractType} from "../../types/basic/_abstract_type";
import {CharacterType, DateType, IntegerType, StringType} from "../../types/basic";
import {TypeUtils} from "../_type_utils";
import {BuiltIn} from "../_builtin";
import {ObjectOriented} from "../_object_oriented";
 
export class Compare {
  public static runSyntax(node: ExpressionNode, input: SyntaxInput): void {
 
    const sourceTypes: (AbstractType | undefined)[] = [];
    const sources = node.findDirectExpressions(Expressions.Source);
    for (const t of sources) {
      sourceTypes.push(Source.runSyntax(t, input));
    }
 
    for (const t of node.findDirectExpressions(Expressions.SourceFieldSymbolChain)) {
      SourceFieldSymbol.runSyntax(t, input);
    }
 
    for (const t of node.findDirectExpressions(Expressions.MethodCallChain)) {
      MethodCallChain.runSyntax(t, input);
    }
 
    const initialMessage = this.checkInitialOperand(node, input);
    if (initialMessage !== undefined) {
      input.issues.push(syntaxIssue(input, node.getFirstToken(), initialMessage));
      return;
    }
 
    const typeUtils = new TypeUtils(input.scope);
    const compareOperator = node.findDirectExpression(Expressions.CompareOperator)?.concatTokens().toUpperCase();
 
    if (compareOperator === "CS"
        && sourceTypes.length === 2
        && sourceTypes.some((type) => typeUtils.isCharLikeForCompare(type) === false)) {
      const message = "CS operands must be character-like (data type C, N, D, T, or STRING)";
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
    } else if (node.findDirectExpression(Expressions.CompareOperator)
        && typeUtils.isCompareable(sourceTypes[0], sourceTypes[1], sources[0], sources[1]) === false
        && sourceTypes.length === 2) {
      let message = "Incompatible types for comparison";
      if ((sourceTypes[0] instanceof DateType
          && sourceTypes[1] instanceof IntegerType
          && sources[1].findFirstExpression(Expressions.ArithOperator))
          || (sourceTypes[1] instanceof DateType
          && sourceTypes[0] instanceof IntegerType
          && sources[0].findFirstExpression(Expressions.ArithOperator))) {
        message = "Date cannot be compared with arithmetic result of type Integer";
      }
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
    }
  }
 
  // a built-in function with a character-like result cannot be the operand of IS [NOT] INITIAL,
  // "Unexpected operator IS" on a system, while one with a numeric result, lines( ), can
  private static checkInitialOperand(node: ExpressionNode, input: SyntaxInput): string | undefined {
    if (input.scope.getOpenABAP()) {
      return undefined;
    }
    const tokens = node.getChildren().filter(c => c instanceof TokenNode).map(c => c.getFirstToken().getStr().toUpperCase());
    if (tokens.includes("IS") === false || tokens[tokens.length - 1] !== "INITIAL") {
      return undefined;
    }
    const chain = node.findDirectExpression(Expressions.Source)?.getChildren();
    // xsdbool( ) and boolc( ) are their own form in the Source grammar, a condition in parentheses
    const first = chain?.[0]?.getFirstToken().getStr().toUpperCase();
    if (chain?.length === 4 && chain[0] instanceof TokenNode && (first === "XSDBOOL" || first === "BOOLC")) {
      return `Built-in function "${chain[0].getFirstToken().getStr()}" cannot be the operand of IS INITIAL`;
    }
    if (chain?.length !== 1 || !(chain[0] instanceof ExpressionNode) || !(chain[0].get() instanceof Expressions.MethodCallChain)) {
      return undefined;
    }
    const calls = chain[0].getChildren();
    if (calls.length !== 1 || !(calls[0] instanceof ExpressionNode) || !(calls[0].get() instanceof Expressions.MethodCall)) {
      return undefined;
    }
    const name = calls[0].findDirectExpression(Expressions.MethodName)?.concatTokens();
    const builtin = BuiltIn.searchBuiltin(name);
    if (name === undefined || builtin === undefined) {
      return undefined;
    }
    // a method of the same name hides the built-in function
    const enclosing = input.scope.findClassDefinition(input.scope.getEnclosingClassName());
    if (enclosing !== undefined && new ObjectOriented(input.scope).searchMethodName(enclosing, name).method !== undefined) {
      return undefined;
    }
    const returning = builtin.getParameters().getReturning()?.getType();
    if (returning instanceof StringType || returning instanceof CharacterType) {
      return `Built-in function "${name}" cannot be the operand of IS INITIAL`;
    }
    return undefined;
  }
 
}