UNPKG

langium

Version:

A language engineering tool for the Language Server Protocol

101 lines (86 loc) 4.62 kB
/****************************************************************************** * Copyright 2021 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ import { NEWLINE_REGEXP } from '../utils/regexp-utils.js'; import { EOL, toString } from './generator-node.js'; export function expandToStringWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { return expandToString(staticParts, ...substitutions) + EOL; } export function expandToStringLFWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { return expandToStringLF(staticParts, ...substitutions) + '\n'; } /** * A tag function that automatically aligns embedded multiline strings. * Multiple lines are joined with the platform-specific line separator. * * @param staticParts the static parts of a tagged template literal * @param substitutions the variable parts of a tagged template literal * @returns an aligned string that consists of the given parts */ export function expandToString(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { return internalExpandToString(EOL, staticParts, ...substitutions); } /** * A tag function that automatically aligns embedded multiline strings. * Multiple lines are joined with the LINE_FEED (`\n`) line separator. * * @param staticParts the static parts of a tagged template literal * @param substitutions the variable parts of a tagged template literal * @returns an aligned string that consists of the given parts */ export function expandToStringLF(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { return internalExpandToString('\n', staticParts, ...substitutions); } function internalExpandToString(lineSep: string, staticParts: TemplateStringsArray, ...substitutions: unknown[]): string { let lines = substitutions // align substitutions and fuse them with static parts .reduce((acc: string, subst: unknown, i: number) => acc + (subst === undefined ? SNLE : align(toString(subst), acc)) + (staticParts[i + 1] ?? ''), staticParts[0]) // converts text to lines .split(NEWLINE_REGEXP) .filter(l => l.trim() !== SNLE) // whitespace-only lines are empty (preserving leading whitespace) .map(l => l.replace(SNLE, '').trimEnd()); // in order to nicely handle single line templates with the leading and trailing termintators (``) on separate lines, like // expandToString`foo // `, // expandToString` // foo // `, // expandToString` // foo`, // the same way as true single line templates like // expandToString`foo` // ... // ... drop initial linebreak if the first line is empty or contains white space only, ... const containsLeadingLinebreak = lines.length > 1 && lines[0].trim().length === 0; lines = containsLeadingLinebreak ? lines.slice(1) : lines; // .. and drop the last linebreak if it's the last charactor or is followed by white space const containsTrailingLinebreak = lines.length !== 0 && lines[lines.length-1].trimEnd().length === 0; lines = containsTrailingLinebreak ? lines.slice(0, lines.length-1) : lines; // finds the minimum indentation const indent = findIndentation(lines); return lines // shifts lines to the left .map(line => line.slice(indent).trimEnd()) // convert lines to string .join(lineSep); } export const SNLE = Object.freeze('__«SKIP^NEW^LINE^IF^EMPTY»__'); const nonWhitespace = /\S|$/; // add the alignment of the previous static part to all lines of the following substitution function align(subst: string, acc: string): string { const length = Math.max(0, acc.length - acc.lastIndexOf('\n') - 1); const indent = ' '.repeat(length); return subst.replace(NEWLINE_REGEXP, EOL + indent); } // finds the indentation of a text block represented by a sequence of lines export function findIndentation(lines: string[]): number { const indents = lines.filter(line => line.length > 0).map(line => line.search(nonWhitespace)); const min = indents.length === 0 ? 0 : Math.min(...indents); // min(...[]) = min() = Infinity return Math.max(0, min); } export function normalizeEOL(input: string): string { return input.replace(NEWLINE_REGEXP, EOL); }