UNPKG

@cowwoc/requirements

Version:

A fluent API for enforcing design contracts with automatic message generation.

109 lines 3.72 kB
import { Type, assertThatType, getSuperclass, internalValueToString } from "../internal.mjs"; class SearchResult { /** * The start index (inclusive) of the matched text. */ start; /** * The end index (exclusive) of the matched text. */ end; constructor(start, end) { this.start = start; this.end = end; } } /** * Returns the last consecutive occurrence of `target` within `source`. * The last occurrence of the empty string `""` is considered to occur at the index value * `source.length()`. * <p> * The returned index is the largest value `k` for which * `source.startsWith(target, k)` consecutively. If no such value of `k` exists, then * `-1` is returned. * * @param source - the string to search within * @param target - the string to search for * @returns the index of the last consecutive occurrence of `target` in `source`, * or `-1` if there is no such occurrence. */ function lastConsecutiveIndexOf(source, target) { assertThatType(source, "source", Type.STRING); assertThatType(target, "target", Type.STRING); const lengthOfTarget = target.length; let result = -1; if (lengthOfTarget === 0) return result; for (let i = source.length - lengthOfTarget; i >= 0; i -= lengthOfTarget) { if (!source.startsWith(target, i)) return result; result = i; } return result; } /** * Returns the last occurrence of `target` in `source`. * * @param source - the string to search within * @param target - the regular expression to search for * @returns null if no match was found */ function lastIndexOf(source, target) { // RegExp is stateful: https://stackoverflow.com/a/11477448/14731 let flags = target.flags; if (!flags.includes("g")) flags += "g"; const matcher = new RegExp(target.source, flags); let match; let searchResult = null; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { match = matcher.exec(source); if (!match) break; searchResult = new SearchResult(match.index, match.index + match[0].length); } return searchResult; } /** * @param source - the string to search within * @param target - the string to search for * @returns true if `source` only contains (potentially multiple) occurrences of * `target` or if `source` is empty */ function containsOnly(source, target) { return source.length === 0 || lastConsecutiveIndexOf(source, target) === 0; } const builtInMapper = (v) => internalValueToString(v); /** * Returns a StringMapper for a value or the closest ancestor that has an associated mapper. * * @param value - a value * @param typeToMapper - a mapping from the name of a type to the string representation of its values * @returns the StringMapper for the value */ function getMapper(value, typeToMapper) { const type = Type.of(value); let mapper = typeToMapper.get(type); if (mapper !== undefined) return mapper; if (type.isPrimitive()) return builtInMapper; const superclass = getSuperclass(value); if (superclass === null) return builtInMapper; const superclassType = Type.of(superclass); mapper = typeToMapper.get(superclassType); if (mapper !== undefined) return mapper; return getMapper(superclass, typeToMapper); } /** * @param value - a string * @returns true if the string does not contain any leading or trailing whitespace */ function valueIsStripped(value) { return /^\S+.*\S+$/.test(value); } export { lastConsecutiveIndexOf, lastIndexOf, containsOnly, getMapper, valueIsStripped }; //# sourceMappingURL=Strings.mjs.map