@cowwoc/requirements
Version:
A fluent API for enforcing design contracts with automatic message generation.
109 lines • 3.72 kB
JavaScript
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