diff options
| author | ST-DDT <[email protected]> | 2022-10-09 21:26:25 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-10-09 21:26:25 +0200 |
| commit | a6ce71703b02f7c2c4f742106acff05d879c4384 (patch) | |
| tree | 0eb6baa6789d1b87a836c49b14b7869e5a3df673 /src/modules | |
| parent | 5dc8f0e82e38215feab362b1a748065c890f570d (diff) | |
| download | faker-a6ce71703b02f7c2c4f742106acff05d879c4384.tar.xz faker-a6ce71703b02f7c2c4f742106acff05d879c4384.zip | |
feat: lorem null response fix (#1407)
Diffstat (limited to 'src/modules')
| -rw-r--r-- | src/modules/lorem/index.ts | 42 | ||||
| -rw-r--r-- | src/modules/word/filterWordListByLength.ts | 98 | ||||
| -rw-r--r-- | src/modules/word/index.ts | 222 |
3 files changed, 305 insertions, 57 deletions
diff --git a/src/modules/lorem/index.ts b/src/modules/lorem/index.ts index 4745b0e9..ac1ba756 100644 --- a/src/modules/lorem/index.ts +++ b/src/modules/lorem/index.ts @@ -1,4 +1,5 @@ import type { Faker } from '../..'; +import { filterWordListByLength } from '../word/filterWordListByLength'; /** * Module to generate random texts and words. @@ -17,24 +18,43 @@ export class LoremModule { /** * Generates a word of a specified length. * - * @param length length of the word that should be returned. Defaults to a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.lorem.word() // 'temporibus' * faker.lorem.word(5) // 'velit' + * faker.lorem.word({ strategy: 'shortest' }) // 'a' + * faker.lorem.word({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'quaerat' * * @since 3.1.0 */ - word(length?: number): string { - const hasRightLength = (word: string) => word.length === length; - let properLengthWords: readonly string[]; - if (length == null) { - properLengthWords = this.faker.definitions.lorem.words; - } else { - properLengthWords = - this.faker.definitions.lorem.words.filter(hasRightLength); - } - return this.faker.helpers.arrayElement(properLengthWords); + word( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; + return this.faker.helpers.arrayElement( + filterWordListByLength({ + ...opts, + wordList: this.faker.definitions.lorem.words, + }) + ); } /** diff --git a/src/modules/word/filterWordListByLength.ts b/src/modules/word/filterWordListByLength.ts new file mode 100644 index 00000000..ca312371 --- /dev/null +++ b/src/modules/word/filterWordListByLength.ts @@ -0,0 +1,98 @@ +import { FakerError } from '../../errors/faker-error'; + +/** + * The error handling strategies for the `filterWordListByLength` function. + * + * Always returns a new array. + */ +const STRATEGIES = { + fail: () => { + throw new FakerError('No words found that match the given length.'); + }, + closest: ( + wordList: string[], + length: { min: number; max: number } + ): string[] => { + const wordsByLength = wordList.reduce((data, word) => { + (data[word.length] = data[word.length] ?? []).push(word); + return data; + }, {} as Record<number, string[]>); + + const lengths = Object.keys(wordsByLength).map(Number); + const min = Math.min(...lengths); + const max = Math.max(...lengths); + + const closestOffset = Math.min(length.min - min, max - length.max); + + return wordList.filter( + (word) => + word.length === length.min - closestOffset || + word.length === length.max + closestOffset + ); + }, + shortest: (wordList: string[]): string[] => { + const minLength = Math.min(...wordList.map((word) => word.length)); + return wordList.filter((word) => word.length === minLength); + }, + longest: (wordList: string[]): string[] => { + const maxLength = Math.max(...wordList.map((word) => word.length)); + return wordList.filter((word) => word.length === maxLength); + }, + 'any-length': (wordList: string[]): string[] => { + return [...wordList]; + }, +} as const; /* +satisfies Record< +string, // Parameters<filterWordListByLength>[0]['strategy'] +(wordList: string[], length: { min: number; max: number }) => string[] +>; +*/ + +/** + * Filters a string array for values with a matching length. + * If length is not provided or no values with a matching length are found, + * then the result will be determined using the given error handling strategy. + * + * @param options The options to provide. + * @param options.wordList A list of words to filter. + * @param options.length The exact or the range of lengths the words should have. + * @param options.strategy The strategy to apply when no words with a matching length are found. Defaults to 'any-length'. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a copy of the original word list. + */ +export function filterWordListByLength(options: { + wordList: string[]; + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; +}): string[] { + const { wordList, length, strategy = 'any-length' } = options; + + if (length) { + const filter: (word: string) => boolean = + typeof length === 'number' + ? (word) => word.length === length + : (word) => word.length >= length.min && word.length <= length.max; + + const wordListWithLengthFilter = wordList.filter(filter); + + if (wordListWithLengthFilter.length > 0) { + return wordListWithLengthFilter; + } + + if (typeof length === 'number') { + return STRATEGIES[strategy](wordList, { min: length, max: length }); + } else { + return STRATEGIES[strategy](wordList, length); + } + } else if (strategy === 'shortest' || strategy === 'longest') { + return STRATEGIES[strategy](wordList); + } else { + return [...wordList]; + } +} diff --git a/src/modules/word/index.ts b/src/modules/word/index.ts index 725c0c42..9bdf8f06 100644 --- a/src/modules/word/index.ts +++ b/src/modules/word/index.ts @@ -1,29 +1,5 @@ import type { Faker } from '../..'; - -/** - * Filters a string array for values with a specific length. - * If length is not provided or no values with this length there found a copy of the original array is returned. - * - * @param options The options to provide - * @param options.wordList A list of word to filter - * @param options.length The exact length words should have - */ -function filterWordListByLength(options: { - wordList: string[]; - length?: number; -}): string[] { - if (!options.length) { - return options.wordList; - } - - const wordListWithLengthFilter = options.wordList.filter( - (word) => word.length === options.length - ); - - return wordListWithLengthFilter.length > 0 - ? wordListWithLengthFilter - : [...options.wordList]; -} +import { filterWordListByLength } from './filterWordListByLength'; /** * Module to return various types of words. @@ -42,20 +18,42 @@ export class WordModule { /** * Returns an adjective of random or optionally specified length. * - * @param length Expected adjective length. If specified length is unresolvable, returns adjective of a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.word.adjective() // 'pungent' * faker.word.adjective(5) // 'slimy' * faker.word.adjective(100) // 'complete' + * faker.word.adjective({ strategy: 'shortest' }) // 'icy' + * faker.word.adjective({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'distant' * * @since 6.0.0 */ - adjective(length?: number): string { + adjective( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; return this.faker.helpers.arrayElement( filterWordListByLength({ + ...opts, wordList: this.faker.definitions.word.adjective, - length, }) ); } @@ -63,20 +61,42 @@ export class WordModule { /** * Returns an adverb of random or optionally specified length. * - * @param length Expected adverb length. If specified length is unresolvable, returns adverb of a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.word.adverb() // 'quarrelsomely' * faker.word.adverb(5) // 'madly' * faker.word.adverb(100) // 'sadly' + * faker.word.adverb({ strategy: 'shortest' }) // 'too' + * faker.word.adverb({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'sweetly' * * @since 6.0.0 */ - adverb(length?: number): string { + adverb( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; return this.faker.helpers.arrayElement( filterWordListByLength({ + ...opts, wordList: this.faker.definitions.word.adverb, - length, }) ); } @@ -84,20 +104,42 @@ export class WordModule { /** * Returns a conjunction of random or optionally specified length. * - * @param length Expected conjunction length. If specified length is unresolvable, returns conjunction of a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.word.conjunction() // 'in order that' * faker.word.conjunction(5) // 'since' * faker.word.conjunction(100) // 'as long as' + * faker.word.conjunction({ strategy: 'shortest' }) // 'or' + * faker.word.conjunction({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'hence' * * @since 6.0.0 */ - conjunction(length?: number): string { + conjunction( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; return this.faker.helpers.arrayElement( filterWordListByLength({ + ...opts, wordList: this.faker.definitions.word.conjunction, - length, }) ); } @@ -105,20 +147,42 @@ export class WordModule { /** * Returns an interjection of random or optionally specified length. * - * @param length Expected interjection length. If specified length is unresolvable, returns interjection of a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.word.interjection() // 'gah' * faker.word.interjection(5) // 'fooey' * faker.word.interjection(100) // 'yowza' + * faker.word.interjection({ strategy: 'shortest' }) // 'hm' + * faker.word.interjection({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'boohoo' * * @since 6.0.0 */ - interjection(length?: number): string { + interjection( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; return this.faker.helpers.arrayElement( filterWordListByLength({ + ...opts, wordList: this.faker.definitions.word.interjection, - length, }) ); } @@ -126,20 +190,42 @@ export class WordModule { /** * Returns a noun of random or optionally specified length. * - * @param length Expected noun length. If specified length is unresolvable, returns noun of a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.word.noun() // 'external' * faker.word.noun(5) // 'front' * faker.word.noun(100) // 'care' + * faker.word.noun({ strategy: 'shortest' }) // 'ad' + * faker.word.noun({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'average' * * @since 6.0.0 */ - noun(length?: number): string { + noun( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; return this.faker.helpers.arrayElement( filterWordListByLength({ + ...opts, wordList: this.faker.definitions.word.noun, - length, }) ); } @@ -147,20 +233,42 @@ export class WordModule { /** * Returns a preposition of random or optionally specified length. * - * @param length Expected preposition length. If specified length is unresolvable, returns preposition of a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.word.preposition() // 'without' * faker.word.preposition(5) // 'abaft' * faker.word.preposition(100) // 'an' + * faker.word.preposition({ strategy: 'shortest' }) // 'a' + * faker.word.preposition({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'given' * * @since 6.0.0 */ - preposition(length?: number): string { + preposition( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; return this.faker.helpers.arrayElement( filterWordListByLength({ + ...opts, wordList: this.faker.definitions.word.preposition, - length, }) ); } @@ -168,20 +276,42 @@ export class WordModule { /** * Returns a verb of random or optionally specified length. * - * @param length Expected verb length. If specified length is unresolvable, returns verb of a random length. + * @param options The expected length of the word or the options to use. + * @param options.length The expected length of the word. + * @param options.strategy The strategy to apply when no words with a matching length are found. + * + * Available error handling strategies: + * + * - `fail`: Throws an error if no words with the given length are found. + * - `shortest`: Returns any of the shortest words. + * - `closest`: Returns any of the words closest to the given length. + * - `longest`: Returns any of the longest words. + * - `any-length`: Returns a word with any length. + * + * Defaults to `'any-length'`. * * @example * faker.word.verb() // 'act' * faker.word.verb(5) // 'tinge' * faker.word.verb(100) // 'mess' + * faker.word.verb({ strategy: 'shortest' }) // 'do' + * faker.word.verb({ length: { min: 5, max: 7 }, strategy: "fail" }) // 'vault' * * @since 6.0.0 */ - verb(length?: number): string { + verb( + options: + | number + | { + length?: number | { min: number; max: number }; + strategy?: 'fail' | 'closest' | 'shortest' | 'longest' | 'any-length'; + } = {} + ): string { + const opts = typeof options === 'number' ? { length: options } : options; return this.faker.helpers.arrayElement( filterWordListByLength({ + ...opts, wordList: this.faker.definitions.word.verb, - length, }) ); } |
