feat(snakeCase): add snakeCase (#152)

* feat(snakeCase): Add caseSplitPattern RegExp const

* feat(snakeCase): Add caseSplitPattern test code

* feat(snakeCase): Add snakeCase function

* feat(snakeCase): Add snakeCase test code

* feat(snakeCase): Add snakeCase docs

* feat(snakeCase): Add snakeCase benchmarks

* chore: Add string export

* fix(snakeCase): constants public api

* Update docs/ko/reference/string/snakeCase.md

* Update docs/ko/reference/string/snakeCase.md

* Update docs/reference/string/snakeCase.md

---------

Co-authored-by: Sojin Park <raon0211@gmail.com>
This commit is contained in:
정해준 2024-07-11 09:14:09 +09:00 committed by GitHub
parent 883553b39b
commit d48900fa55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 234 additions and 1 deletions

View File

@ -5,3 +5,4 @@ echo "export * from './dist/math';" > math.d.ts
echo "export * from './dist/object';" > object.d.ts echo "export * from './dist/object';" > object.d.ts
echo "export * from './dist/predicate';" > predicate.d.ts echo "export * from './dist/predicate';" > predicate.d.ts
echo "export * from './dist/promise';" > promise.d.ts echo "export * from './dist/promise';" > promise.d.ts
echo "export * from './dist/string';" > string.d.ts

View File

@ -0,0 +1,15 @@
import { bench, describe } from 'vitest';
import { snakeCase as snakeCaseToolkit } from 'es-toolkit';
import { snakeCase as snakeCaseLodash } from 'lodash';
describe('snakeCase', () => {
bench('es-toolkit/snakeCase', () => {
const str = 'camleCase';
snakeCaseToolkit(str);
});
bench('lodash/snakeCase', () => {
const str = 'camelCase';
snakeCaseLodash(str);
});
});

View File

@ -142,6 +142,10 @@ function sidebar(): DefaultTheme.Sidebar {
text: 'Promise Utilities', text: 'Promise Utilities',
items: [{ text: 'delay', link: '/reference/promise/delay' }], items: [{ text: 'delay', link: '/reference/promise/delay' }],
}, },
{
text: 'String Utilities',
items: [{ text: 'snakeCase', link: '/reference/string/snakeCase' }],
},
], ],
}, },
]; ];

View File

@ -141,6 +141,10 @@ function sidebar(): DefaultTheme.Sidebar {
text: 'Promise', text: 'Promise',
items: [{ text: 'delay', link: '/ko/reference/promise/delay' }], items: [{ text: 'delay', link: '/ko/reference/promise/delay' }],
}, },
{
text: '문자열',
items: [{ text: 'snakeCase', link: '/ko/reference/string/snakeCase' }],
},
], ],
}, },
]; ];

View File

@ -16,6 +16,7 @@ Here are some of the features es-toolkit offers:
- **Object**: Tools for manipulating JavaScript objects, such as [pick](./reference/object/pick.md) and [omit](./reference/object/omit.md). - **Object**: Tools for manipulating JavaScript objects, such as [pick](./reference/object/pick.md) and [omit](./reference/object/omit.md).
- **Predicate**: Type guard functions like [isNotNil](./reference/predicate/isNotNil.md). - **Predicate**: Type guard functions like [isNotNil](./reference/predicate/isNotNil.md).
- **Promise**: Asynchronous utilities like [delay](./reference/promise/delay.md). - **Promise**: Asynchronous utilities like [delay](./reference/promise/delay.md).
- **String**: Utilties for string manipulation, such as [snakeCase](./reference/string/snakeCase.md)
## Links ## Links

View File

@ -17,6 +17,7 @@ es-toolkit이 제공하는 기능 목록은 다음과 같습니다.
- **객체**: [pick](./reference/object/pick.md)이나 [omit](./reference/object/omit.md)처럼 JavaScript 객체를 다루는 함수를 제공해요. - **객체**: [pick](./reference/object/pick.md)이나 [omit](./reference/object/omit.md)처럼 JavaScript 객체를 다루는 함수를 제공해요.
- **타입 가드**: [isNotNil](./reference/predicate/isNotNil.md)처럼 특정한 객체가 어떤 상태인지 검사하는 타입 가드 함수를 제공해요. - **타입 가드**: [isNotNil](./reference/predicate/isNotNil.md)처럼 특정한 객체가 어떤 상태인지 검사하는 타입 가드 함수를 제공해요.
- **Promise**: [delay](./reference/promise/delay.md)와 같은 비동기 유틸리티 함수를 제공해요. - **Promise**: [delay](./reference/promise/delay.md)와 같은 비동기 유틸리티 함수를 제공해요.
- **문자열**: [snakeCase](./reference/string/snakeCase.md)와 같이 문자열을 다루기 위한 다양한 함수를 제공해요.
## 링크 ## 링크

View File

@ -0,0 +1,30 @@
# snakeCase
문자열을 스네이크 표기법으로 변환해요.
스네이크 표기법은 여러 단어로 구성된 식별자에서 각 단어를 소문자로 작성하고, 단어 사이를 밑줄(\_)로 연결하는 명명 규칙이에요. 예를 들어서, `snake_case` 처럼 작성해요.
## 인터페이스
```typescript
function snakeCase(str: string): string;
```
### 파라미터
- `str` (`string`): 스네이크 케이스로 변환할 문자열이에요.
### 반환 값
(`string`) 스네이크 케이스로 변환된 문자열이에요.
## 예시
```typescript
import { snakeCase } from 'es-toolkit/string';
snakeCase('camelCase'); // returns 'camel_case'
snakeCase('some whitespace'); // returns 'some_whitespace'
snakeCase('hyphen-text'); // returns 'hyphen_text'
snakeCase('HTTPRequest'); // returns 'http_request'
```

View File

@ -0,0 +1,30 @@
# snakeCase
Converts a string to snake case.
Snake case is the naming convention in which each word is written in lowercase and separated by an underscore (\_) character. For example, `snake_case`.
## Signature
```typescript
function snakeCase(str: string): string;
```
### Parameters
- `str` (`string`): The string that is to be changed to snake case.
### Returns
(`string`) The converted string to snake case.
## Examples
```typescript
import { snakeCase } from 'es-toolkit/string';
snakeCase('camelCase'); // returns 'camel_case'
snakeCase('some whitespace'); // returns 'some_whitespace'
snakeCase('hyphen-text'); // returns 'hyphen_text'
snakeCase('HTTPRequest'); // returns 'http_request'
```

View File

@ -21,6 +21,7 @@
"./object": "./src/object/index.ts", "./object": "./src/object/index.ts",
"./predicate": "./src/predicate/index.ts", "./predicate": "./src/predicate/index.ts",
"./promise": "./src/promise/index.ts", "./promise": "./src/promise/index.ts",
"./string": "./src/string/index.ts",
"./package.json": "./package.json" "./package.json": "./package.json"
}, },
"files": [ "files": [
@ -103,6 +104,16 @@
"default": "./dist/promise/index.js" "default": "./dist/promise/index.js"
} }
}, },
"./string": {
"import": {
"types": "./dist/string/index.d.mts",
"default": "./dist/string/index.mjs"
},
"require": {
"types": "./dist/string/index.d.ts",
"default": "./dist/string/index.js"
}
},
"./package.json": "./package.json" "./package.json": "./package.json"
} }
}, },

View File

@ -0,0 +1,57 @@
import { describe, expect, it } from 'vitest';
import { CASE_SPLIT_PATTERN } from './caseSplitPattern';
describe('caseSplitPattern', () => {
it('should match camelCase', async () => {
const str = 'camelCase';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['camel', 'Case']);
});
it('should match snake_case', async () => {
const str = 'snake_case';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['snake', 'case']);
});
it('should match kebab-case', async () => {
const str = 'kebab-case';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['kebab', 'case']);
});
it('should handle mixed formats', async () => {
const str = 'camelCase_snake_case-kebabCase';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['camel', 'Case', 'snake', 'case', 'kebab', 'Case']);
});
it('should match acronyms', async () => {
const str = 'HTTPRequest';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['HTTP', 'Request']);
});
it('should match special characters', async () => {
const str = 'special_characters@123';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['special', 'characters', '123']);
});
it('should handle leading and trailing whitespace', async () => {
const str = ' leading_and_trailing_whitespace ';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['leading', 'and', 'trailing', 'whitespace']);
});
it('should handle underscores', async () => {
const str = 'underscore_case_example';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['underscore', 'case', 'example']);
});
it('should handle single character words', async () => {
const str = 'aB';
const matches = str.match(CASE_SPLIT_PATTERN);
expect(matches).toEqual(['a', 'B']);
});
});

View File

@ -0,0 +1,18 @@
/**
* Regular expression pattern to split strings into words for various case conversions
*
* This pattern matchs sequences of characters in a string, considering the following case:
* - Sequences of two or more uppercase letters followed by an uppercase letter and lowercase letters or digits (for acronyms)
* - Sequences of one uppercase letter optionally followed by lowercase letters and digits
* - Single uppercase letters
* - Sequences of digis
*
* The resulting match can be used to convert camelCase, snake_case, kebab-case, and other mixed formats into
* a consistent format like snake case.
*
* @example
* const matches = 'camelCaseHTTPRequest'.match(CASE_SPLIT_PATTERN);
* // matchs: ['camel', 'Case', 'HTTP', 'Request']
*/
export const CASE_SPLIT_PATTERN = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g;

1
src/constants/index.ts Normal file
View File

@ -0,0 +1 @@
export { CASE_SPLIT_PATTERN } from './caseSplitPattern';

View File

@ -20,6 +20,7 @@
* - **Object**: Tools for manipulating JavaScript objects, such as [pick](https://es-toolkit.slash.page/reference/object/pick.html) and [omit](https://es-toolkit.slash.page/reference/object/omit.html). * - **Object**: Tools for manipulating JavaScript objects, such as [pick](https://es-toolkit.slash.page/reference/object/pick.html) and [omit](https://es-toolkit.slash.page/reference/object/omit.html).
* - **Predicate**: Type guard functions like [isNotNil](https://es-toolkit.slash.page/reference/predicate/isNotNil.html). * - **Predicate**: Type guard functions like [isNotNil](https://es-toolkit.slash.page/reference/predicate/isNotNil.html).
* - **Promise**: Asynchronous utilities like [delay](https://es-toolkit.slash.page/reference/promise/delay.html). * - **Promise**: Asynchronous utilities like [delay](https://es-toolkit.slash.page/reference/promise/delay.html).
* - **String**: Utilities for string manipulation, such as [snakeCase](https://es-toolkit.slash.page/reference/string/snakeCase.html)
* *
* If you want to know more about the project, please take a look at the * If you want to know more about the project, please take a look at the
* following resources: * following resources:
@ -36,3 +37,4 @@ export * from './math/index.ts';
export * from './object/index.ts'; export * from './object/index.ts';
export * from './predicate/index.ts'; export * from './predicate/index.ts';
export * from './promise/index.ts'; export * from './promise/index.ts';
export * from './string/index.ts'

1
src/string/index.ts Normal file
View File

@ -0,0 +1 @@
export { snakeCase } from './snakeCase.ts';

View File

@ -0,0 +1,36 @@
import { describe, it, expect } from 'vitest';
import { snakeCase } from './snakeCase';
describe('snakeCase', () => {
it('should change camel case to snake case', async () => {
expect(snakeCase('camelCase')).toEqual('camel_case');
});
it('should change space to underscore', async () => {
expect(snakeCase('some whitespace')).toEqual('some_whitespace');
});
it('should change hyphen to underscore', async () => {
expect(snakeCase('hyphen-text')).toEqual('hyphen_text');
});
it('should change Acronyms to small letter', async () => {
expect(snakeCase('HTTPRequest')).toEqual('http_request');
});
it('should handle leading and trailing whitepspace', async () => {
expect(snakeCase(' leading and trailing whitespace')).toEqual('leading_and_trailing_whitespace');
});
it('should handle special characters correctly', async () => {
expect(snakeCase('special@characters!')).toEqual('special_characters');
});
it('should handle strings that are already in snake_case', async () => {
expect(snakeCase('snake_case')).toEqual('snake_case');
});
it('should work with an empty string', async () => {
expect(snakeCase('')).toEqual('');
});
});

21
src/string/snakeCase.ts Normal file
View File

@ -0,0 +1,21 @@
import { CASE_SPLIT_PATTERN } from '../constants';
/**
* Converts a string to snake case.
*
* Snake case is the naming convention in which each word is written in lowercase and separated by an underscore (_) character.
*
* @param {string} str - The string that is to be changed to snake case.
* @returns {string} - The converted string to snake case.
*
* @example
* const convertedStr1 = snakeCase('camelCase') // returns 'camel_case'
* const convertedStr2 = snakeCase('some whitespace') // returns 'some_whitespace'
* const convertedStr3 = snakeCase('hyphen-text') // returns 'hyphen_text'
* const convertedStr4 = snakeCase('HTTPRequest') // returns 'http_request'
*/
export const snakeCase = (str: string): string => {
const splitWords = str.match(CASE_SPLIT_PATTERN) || [];
return splitWords.map(word => word.toLowerCase()).join('_');
};