Skip to content

Commit

Permalink
Allow split function option
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeembrey committed Jan 8, 2024
1 parent f6ce967 commit b7afd3c
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 65 deletions.
31 changes: 10 additions & 21 deletions packages/change-case/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {
sentenceCase,
snakeCase,
split,
splitSeparateNumbers,
trainCase,
Options,
} from "./index.js";

type Result = {
split: string[];
camelCase: string;
capitalCase: string;
constantCase: string;
Expand All @@ -35,7 +35,6 @@ const tests: [string, Result, Options?][] = [
[
"",
{
split: [],
camelCase: "",
capitalCase: "",
constantCase: "",
Expand All @@ -53,7 +52,6 @@ const tests: [string, Result, Options?][] = [
[
"test",
{
split: ["test"],
camelCase: "test",
capitalCase: "Test",
constantCase: "TEST",
Expand All @@ -71,7 +69,6 @@ const tests: [string, Result, Options?][] = [
[
"test string",
{
split: ["test", "string"],
camelCase: "testString",
capitalCase: "Test String",
constantCase: "TEST_STRING",
Expand All @@ -89,7 +86,6 @@ const tests: [string, Result, Options?][] = [
[
"Test String",
{
split: ["Test", "String"],
camelCase: "testString",
capitalCase: "Test String",
constantCase: "TEST_STRING",
Expand All @@ -107,7 +103,6 @@ const tests: [string, Result, Options?][] = [
[
"Test String",
{
split: ["Test", "String"],
camelCase: "test$String",
capitalCase: "Test$String",
constantCase: "TEST$STRING",
Expand All @@ -128,7 +123,6 @@ const tests: [string, Result, Options?][] = [
[
"TestV2",
{
split: ["Test", "V2"],
camelCase: "testV2",
capitalCase: "Test V2",
constantCase: "TEST_V2",
Expand All @@ -146,7 +140,6 @@ const tests: [string, Result, Options?][] = [
[
"_foo_bar_",
{
split: ["foo", "bar"],
camelCase: "fooBar",
capitalCase: "Foo Bar",
constantCase: "FOO_BAR",
Expand All @@ -164,7 +157,6 @@ const tests: [string, Result, Options?][] = [
[
"version 1.2.10",
{
split: ["version", "1", "2", "10"],
camelCase: "version_1_2_10",
capitalCase: "Version 1 2 10",
constantCase: "VERSION_1_2_10",
Expand All @@ -182,7 +174,6 @@ const tests: [string, Result, Options?][] = [
[
"version 1.21.0",
{
split: ["version", "1", "21", "0"],
camelCase: "version_1_21_0",
capitalCase: "Version 1 21 0",
constantCase: "VERSION_1_21_0",
Expand All @@ -200,7 +191,6 @@ const tests: [string, Result, Options?][] = [
[
"TestV2",
{
split: ["Test", "V", "2"],
camelCase: "testV_2",
capitalCase: "Test V 2",
constantCase: "TEST_V_2",
Expand All @@ -214,12 +204,13 @@ const tests: [string, Result, Options?][] = [
snakeCase: "test_v_2",
trainCase: "Test-V-2",
},
{ separateNumbers: true },
{
separateNumbers: true,
},
],
[
"1test",
{
split: ["1", "test"],
camelCase: "1Test",
capitalCase: "1 Test",
constantCase: "1_TEST",
Expand All @@ -238,7 +229,6 @@ const tests: [string, Result, Options?][] = [
[
"Foo12019Bar",
{
split: ["Foo", "12019", "Bar"],
camelCase: "foo_12019Bar",
capitalCase: "Foo 12019 Bar",
constantCase: "FOO_12019_BAR",
Expand All @@ -257,7 +247,6 @@ const tests: [string, Result, Options?][] = [
[
"aNumber2in",
{
split: ["a", "Number", "2", "in"],
camelCase: "aNumber_2In",
capitalCase: "A Number 2 In",
constantCase: "A_NUMBER_2_IN",
Expand All @@ -276,7 +265,6 @@ const tests: [string, Result, Options?][] = [
[
"V1Test",
{
split: ["V1", "Test"],
camelCase: "v1Test",
capitalCase: "V1 Test",
constantCase: "V1_TEST",
Expand All @@ -294,7 +282,6 @@ const tests: [string, Result, Options?][] = [
[
"V1Test with separateNumbers",
{
split: ["V", "1", "Test", "with", "separate", "Numbers"],
camelCase: "v_1TestWithSeparateNumbers",
capitalCase: "V 1 Test With Separate Numbers",
constantCase: "V_1_TEST_WITH_SEPARATE_NUMBERS",
Expand All @@ -313,7 +300,6 @@ const tests: [string, Result, Options?][] = [
[
"__typename",
{
split: ["typename"],
camelCase: "__typename",
capitalCase: "__Typename",
constantCase: "__TYPENAME",
Expand All @@ -334,7 +320,6 @@ const tests: [string, Result, Options?][] = [
[
"type__",
{
split: ["type"],
camelCase: "type__",
capitalCase: "Type__",
constantCase: "TYPE__",
Expand All @@ -355,7 +340,6 @@ const tests: [string, Result, Options?][] = [
[
"__type__",
{
split: ["type"],
camelCase: "__type__",
capitalCase: "__Type__",
constantCase: "__TYPE__",
Expand All @@ -379,7 +363,6 @@ const tests: [string, Result, Options?][] = [
describe("change case", () => {
for (const [input, result, options] of tests) {
it(input, () => {
expect(split(input, options)).toEqual(result.split);
expect(camelCase(input, options)).toEqual(result.camelCase);
expect(capitalCase(input, options)).toEqual(result.capitalCase);
expect(constantCase(input, options)).toEqual(result.constantCase);
Expand All @@ -394,6 +377,12 @@ describe("change case", () => {
});
}

describe("split", () => {
it("should split an empty string", () => {
expect(split("")).toEqual([]);
});
});

describe("pascal case merge option", () => {
it("should merge numbers", () => {
const input = "version 1.2.10";
Expand Down
86 changes: 42 additions & 44 deletions packages/change-case/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Regexps involved with splitting words in various case formats.
const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu;
const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu;
const SPLIT_NUMBER_LOWER_RE = /(\d)(\p{Ll})/gu;
const SPLIT_LETTER_NUMBER_RE = /(\p{L})(\d)/gu;

// Used to iterate over the initial split result and separate numbers.
const SPLIT_SEPARATE_NUMBER_RE = /(?<=\d)(\p{Ll})|(?<=\p{L})(\d)/u;

// Regexp involved with stripping non-word characters from the result.
const DEFAULT_STRIP_REGEXP = /[^\p{L}\d]+/giu;
Expand All @@ -29,36 +30,26 @@ export interface PascalCaseOptions extends Options {
/**
* Options used for converting strings to any case.
*/
export interface Options extends SplitOptions {
export interface Options {
locale?: Locale;
split?: (value: string) => string[];
/** @deprecated Pass `split: splitSeparateNumbers` instead. */
separateNumbers?: boolean;
delimiter?: string;
prefixCharacters?: string;
suffixCharacters?: string;
}

/**
* Options used for splitting strings into word segments.
*/
export interface SplitOptions {
separateNumbers?: boolean;
}

/**
* Split any cased input strings into an array of words.
*/
export function split(value: string, options?: SplitOptions) {
export function split(value: string) {
let result = value.trim();

result = result
.replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE)
.replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE);

if (options?.separateNumbers) {
result = result
.replace(SPLIT_NUMBER_LOWER_RE, SPLIT_REPLACE_VALUE)
.replace(SPLIT_LETTER_NUMBER_RE, SPLIT_REPLACE_VALUE);
}

result = result.replace(DEFAULT_STRIP_REGEXP, "\0");

let start = 0;
Expand All @@ -72,16 +63,29 @@ export function split(value: string, options?: SplitOptions) {
return result.slice(start, end).split(/\0/g);
}

/**
* Split the input string into an array of words, separating numbers.
*/
export function splitSeparateNumbers(value: string) {
const words = split(value);
for (let i = 0; i < words.length; i++) {
const word = words[i];
const match = SPLIT_SEPARATE_NUMBER_RE.exec(word);
if (match) {
words.splice(i, 1, word.slice(0, match.index), word.slice(match.index));
}
}
return words;
}

/**
* Convert a string to space separated lower case (`foo bar`).
*/
export function noCase(input: string, options?: Options) {
const [prefix, value, suffix] = splitPrefixSuffix(input, options);
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
return (
prefix +
split(value, options)
.map(lowerFactory(options?.locale))
.join(options?.delimiter ?? " ") +
words.map(lowerFactory(options?.locale)).join(options?.delimiter ?? " ") +
suffix
);
}
Expand All @@ -90,15 +94,15 @@ export function noCase(input: string, options?: Options) {
* Convert a string to camel case (`fooBar`).
*/
export function camelCase(input: string, options?: PascalCaseOptions) {
const [prefix, value, suffix] = splitPrefixSuffix(input, options);
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
const lower = lowerFactory(options?.locale);
const upper = upperFactory(options?.locale);
const transform = options?.mergeAmbiguousCharacters
? capitalCaseTransformFactory(lower, upper)
: pascalCaseTransformFactory(lower, upper);
return (
prefix +
split(value, options)
words
.map((word, index) => {
if (index === 0) return lower(word);
return transform(word, index);
Expand All @@ -112,19 +116,13 @@ export function camelCase(input: string, options?: PascalCaseOptions) {
* Convert a string to pascal case (`FooBar`).
*/
export function pascalCase(input: string, options?: PascalCaseOptions) {
const [prefix, value, suffix] = splitPrefixSuffix(input, options);
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
const lower = lowerFactory(options?.locale);
const upper = upperFactory(options?.locale);
const transform = options?.mergeAmbiguousCharacters
? capitalCaseTransformFactory(lower, upper)
: pascalCaseTransformFactory(lower, upper);
return (
prefix +
split(value, options)
.map(transform)
.join(options?.delimiter ?? "") +
suffix
);
return prefix + words.map(transform).join(options?.delimiter ?? "") + suffix;
}

/**
Expand All @@ -138,12 +136,12 @@ export function pascalSnakeCase(input: string, options?: Options) {
* Convert a string to capital case (`Foo Bar`).
*/
export function capitalCase(input: string, options?: Options) {
const [prefix, value, suffix] = splitPrefixSuffix(input, options);
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
const lower = lowerFactory(options?.locale);
const upper = upperFactory(options?.locale);
return (
prefix +
split(value, options)
words
.map(capitalCaseTransformFactory(lower, upper))
.join(options?.delimiter ?? " ") +
suffix
Expand All @@ -154,12 +152,10 @@ export function capitalCase(input: string, options?: Options) {
* Convert a string to constant case (`FOO_BAR`).
*/
export function constantCase(input: string, options?: Options) {
const [prefix, value, suffix] = splitPrefixSuffix(input, options);
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
return (
prefix +
split(value, options)
.map(upperFactory(options?.locale))
.join(options?.delimiter ?? "_") +
words.map(upperFactory(options?.locale)).join(options?.delimiter ?? "_") +
suffix
);
}
Expand Down Expand Up @@ -189,13 +185,13 @@ export function pathCase(input: string, options?: Options) {
* Convert a string to path case (`Foo bar`).
*/
export function sentenceCase(input: string, options?: Options) {
const [prefix, value, suffix] = splitPrefixSuffix(input, options);
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
const lower = lowerFactory(options?.locale);
const upper = upperFactory(options?.locale);
const transform = capitalCaseTransformFactory(lower, upper);
return (
prefix +
split(value, options)
words
.map((word, index) => {
if (index === 0) return transform(word);
return lower(word);
Expand Down Expand Up @@ -252,12 +248,14 @@ function pascalCaseTransformFactory(

function splitPrefixSuffix(
input: string,
options: Options | undefined,
): [string, string, string] {
options: Options = {},
): [string, string[], string] {
const splitFn =
options.split ?? (options.separateNumbers ? splitSeparateNumbers : split);
const prefixCharacters =
options?.prefixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
options.prefixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
const suffixCharacters =
options?.suffixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
options.suffixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
let prefixIndex = 0;
let suffixIndex = input.length;

Expand All @@ -276,7 +274,7 @@ function splitPrefixSuffix(

return [
input.slice(0, prefixIndex),
input.slice(prefixIndex, suffixIndex),
splitFn(input.slice(prefixIndex, suffixIndex)),
input.slice(suffixIndex),
];
}

0 comments on commit b7afd3c

Please sign in to comment.