superflected
Version:
A port of ActiveSupport's inflector to Node.js
44 lines (37 loc) • 1.47 kB
text/typescript
import { cacheable } from "./cacheable";
import { inflections } from "./Inflector";
const letterOrDigit = /[A-Za-z\d]/;
const wordBoundaryOrNonLetter = /\b|[^a-z]/;
const boundaryMatcher = /([A-Z\d]+)([A-Z][a-z])|([a-z\d])([A-Z])|(-)/g;
export const underscore = cacheable((camelCasedWord: string) => {
let result = camelCasedWord;
const acronymMatches = inflections().casedAcronymMatcher?.search(camelCasedWord, isWordBoundary);
if (acronymMatches) {
acronymMatches.forEach(([pos, match], index) => {
if (index > 0) {
pos = pos + index - 1;
}
const beforeCharacter = result[pos - match.length];
const afterCharacter = result[pos + 1];
if ((pos == match.length - 1 || letterOrDigit.test(beforeCharacter)) && wordBoundaryOrNonLetter.test(afterCharacter)) {
if (pos > match.length - 1) {
result = `${result.slice(0, pos - match.length + 1)}_${match.toLowerCase()}${result.slice(pos + 1)}`;
} else {
result = `${match.toLowerCase()}${result.slice(pos + 1)}`;
}
}
});
}
return result
.replace(boundaryMatcher, (_match, p1, p2, p3, p4, p5) => {
if (p1) return `${p1}_${p2}`;
if (p5) return `_`;
return `${p3}_${p4}`;
})
.toLowerCase();
});
function isWordBoundary(char: string): boolean {
const charCode = char.charCodeAt(0);
const isLowercaseLetter = charCode >= 97 && charCode <= 122; // a-z
return !isLowercaseLetter;
}