All files / src/language spel-formatter.ts

100% Statements 40/40
93.1% Branches 27/29
100% Functions 6/6
100% Lines 39/39

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                      16x           16x 16x 7x 7x 7x 7x 7x 7x 2x   3x   2x       16x 13x 13x 13x 86x 86x 86x 6x 3x 3x 3x     86x 31x 31x   55x 26x 8x   26x   29x   13x     16x 5x 5x 5x   2x         2x             12x      
import { SpelExpressionParser } from '../spel-expression-parser.js';
 
/**
 * Options for SpEL expression formatting.
 */
export interface FormatOptions {
  indentSize?: number;
  spacing?: 'always' | 'compact';
  maxLineWidth?: number;
}
 
const DEFAULT_OPTIONS: Required<FormatOptions> = {
  indentSize: 2,
  spacing: 'always',
  maxLineWidth: 120,
};
 
export namespace SpelFormatter {
  export function format(expression: string, options?: FormatOptions): string {
    const opts = { ...DEFAULT_OPTIONS, ...options };
    try {
      const parser = new SpelExpressionParser();
      const ast = parser.parseRaw(expression);
      const formatted = ast.toStringAST();
      if (opts.spacing === 'compact') {
        return compactSpaces(formatted);
      }
      return formatted;
    } catch {
      return SpelFormatter.minify(expression);
    }
  }
 
  export function minify(expression: string): string {
    let inString: string | null = null;
    let result = '';
    for (let i = 0; i < expression.length; i++) {
      const ch = expression[i] ?? '';
      const prev = result[result.length - 1];
      if ((ch === "'" || ch === '"') && (i === 0 || expression[i - 1] !== '\\')) {
        if (inString === null) {
          inString = ch;
        E} else if (inString === ch) {
          inString = null;
        }
      }
      if (inString !== null) {
        result += ch;
        continue;
      }
      if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
        if (prev && prev !== ' ' && isTokenChar(prev)) {
          result += ' ';
        }
        continue;
      }
      result += ch;
    }
    return result.trim();
  }
 
  export function semanticallyEqual(a: string, b: string): boolean {
    try {
      const parser = new SpelExpressionParser();
      return parser.parseRaw(a).toStringAST() === parser.parseRaw(b).toStringAST();
    } catch {
      return SpelFormatter.minify(a) === SpelFormatter.minify(b);
    }
  }
 
  function compactSpaces(expr: string): string {
    return expr
      .replace(/\s*([+\-*/%=<>!&|^?:.,;()[\]{}])\s*/g, '$1')
      .replace(/\s+(and|or|not|eq|ne|lt|gt|le|ge|mod|matches|between|instanceof|new)\s+/gi, ' $1 ')
      .trim();
  }
 
  function isTokenChar(ch: string): boolean {
    return /[\w#@]/.test(ch);
  }
}