UNPKG

eslint-plugin-perfectionist

Version:

ESLint plugin for sorting various data such as objects, imports, types, enums, JSX props, etc.

385 lines (384 loc) 13.1 kB
'use strict' var __defProp = Object.defineProperty var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value, }) : (obj[key] = value) var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== 'symbol' ? key + '' : key, value) Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }) const naturalOrderby = require('natural-orderby') const convertBooleanToSign = require('./convert-boolean-to-sign.js') class Alphabet { constructor(characters) { __publicField(this, '_characters', []) this._characters = characters } /** * Generates an alphabet from the given characters. * @param {string|string[]} values - The characters to generate the alphabet from * @returns {Alphabet} - The wrapped alphabet */ static generateFrom(values) { let arrayValues = typeof values === 'string' ? [...values] : values if (arrayValues.length !== new Set(arrayValues).size) { throw new Error('The alphabet must not contain repeated characters') } if (arrayValues.some(value => value.length !== 1)) { throw new Error('The alphabet must contain single characters') } return new Alphabet( arrayValues.map(value => Alphabet._convertCodepointToCharacter(value.codePointAt(0)), ), ) } /** * Generates an alphabet containing relevant characters from the Unicode * standard. Contains the Unicode planes 0 and 1. * @returns {Alphabet} - The generated alphabet */ static generateRecommendedAlphabet() { return Alphabet._generateAlphabetToRange(131071 + 1) } /** * Generates an alphabet containing all characters from the Unicode standard * except for irrelevant Unicode planes. * Contains the Unicode planes 0, 1, 2 and 3. * @returns {Alphabet} - The generated alphabet */ static generateCompleteAlphabet() { return Alphabet._generateAlphabetToRange(262143 + 1) } static _convertCodepointToCharacter(codePoint) { let character = String.fromCodePoint(codePoint) let lowercaseCharacter = character.toLowerCase() let uppercaseCharacter = character.toUpperCase() return { value: character, codePoint, ...(lowercaseCharacter === character ? null : { lowercaseCharacterCodePoint: lowercaseCharacter.codePointAt(0), }), ...(uppercaseCharacter === character ? null : { uppercaseCharacterCodePoint: uppercaseCharacter.codePointAt(0), }), } } /** * Generates an alphabet containing relevant characters from the Unicode * standard. * @param {number} maxCodePoint - The maximum code point to generate the * alphabet to * @returns {Alphabet} - The generated alphabet */ static _generateAlphabetToRange(maxCodePoint) { let totalCharacters = Array.from({ length: maxCodePoint }, (_, i) => Alphabet._convertCodepointToCharacter(i), ) return new Alphabet(totalCharacters) } /** * For each character with a lower and upper case, permutes the two cases * so that the alphabet is ordered by the case priority entered. * @param {string} casePriority - The case to prioritize * @returns {Alphabet} - The same alphabet instance with the cases prioritized * @example * Alphabet.generateFrom('aAbBcdCD') * .prioritizeCase('uppercase') // Returns 'AaBbCDcd'. */ prioritizeCase(casePriority) { let charactersWithCase = this._getCharactersWithCase() let parsedIndexes = /* @__PURE__ */ new Set() let indexByCodePoints = this._characters.reduce( (indexByCodePoint, character, index) => { indexByCodePoint[character.codePoint] = index return indexByCodePoint }, {}, ) for (let { character, index } of charactersWithCase) { if (parsedIndexes.has(index)) { continue } parsedIndexes.add(index) let otherCharacterIndex = indexByCodePoints[ character.uppercaseCharacterCodePoint ?? character.lowercaseCharacterCodePoint ] if (otherCharacterIndex === void 0) { continue } parsedIndexes.add(otherCharacterIndex) let isCharacterUppercase = !character.uppercaseCharacterCodePoint if (isCharacterUppercase) { if ( (casePriority === 'uppercase' && index < otherCharacterIndex) || (casePriority === 'lowercase' && index > otherCharacterIndex) ) { continue } } else { if ( (casePriority === 'uppercase' && index > otherCharacterIndex) || (casePriority === 'lowercase' && index < otherCharacterIndex) ) { continue } } this._characters[index] = this._characters[otherCharacterIndex] this._characters[otherCharacterIndex] = character } return this } /** * Adds specific characters to the end of the alphabet. * @param {string|string[]} values - The characters to push to the alphabet * @returns {Alphabet} - The same alphabet instance without the specified * characters * @example * Alphabet.generateFrom('ab') * .pushCharacters('cd') * // Returns 'abcd' */ pushCharacters(values) { let arrayValues = typeof values === 'string' ? [...values] : values let valuesSet = new Set(arrayValues) let valuesAlreadyExisting = this._characters.filter(({ value }) => valuesSet.has(value), ) if (valuesAlreadyExisting.length > 0) { throw new Error( `The alphabet already contains the characters ${valuesAlreadyExisting .slice(0, 5) .map(({ value }) => value) .join(', ')}`, ) } if (arrayValues.some(value => value.length !== 1)) { throw new Error('Only single characters may be pushed') } this._characters.push( ...[...valuesSet].map(value => Alphabet._convertCodepointToCharacter(value.codePointAt(0)), ), ) return this } /** * Permutes characters with cases so that all characters with the entered case * are put before the other characters. * @param {string} caseToComeFirst - The case to put before the other * characters * @returns {Alphabet} - The same alphabet instance with all characters with * case before all the characters with the other case */ placeAllWithCaseBeforeAllWithOtherCase(caseToComeFirst) { let charactersWithCase = this._getCharactersWithCase() let orderedCharacters = [ ...charactersWithCase.filter(character => caseToComeFirst === 'uppercase' ? !character.character.uppercaseCharacterCodePoint : character.character.uppercaseCharacterCodePoint, ), ...charactersWithCase.filter(character => caseToComeFirst === 'uppercase' ? character.character.uppercaseCharacterCodePoint : !character.character.uppercaseCharacterCodePoint, ), ] for (let [i, element] of charactersWithCase.entries()) { this._characters[element.index] = orderedCharacters[i].character } return this } /** * Places a specific character right before another character in the alphabet. * @param {object} params - The parameters for the operation * @param {string} params.characterBefore - The character to come before * characterAfter * @param {string} params.characterAfter - The target character * @returns {Alphabet} - The same alphabet instance with the specific * character prioritized * @example * Alphabet.generateFrom('ab-cd/') * .placeCharacterBefore({ characterBefore: '/', characterAfter: '-' }) * // Returns 'ab/-cd' */ placeCharacterBefore({ characterBefore, characterAfter }) { return this._placeCharacterBeforeOrAfter({ characterBefore, characterAfter, type: 'before', }) } /** * Places a specific character right after another character in the alphabet. * @param {object} params - The parameters for the operation * @param {string} params.characterBefore - The target character * @param {string} params.characterAfter - The character to come after * characterBefore * @returns {Alphabet} - The same alphabet instance with the specific * character prioritized * @example * Alphabet.generateFrom('ab-cd/') * .placeCharacterAfter({ characterBefore: '/', characterAfter: '-' }) * // Returns 'abcd/-' */ placeCharacterAfter({ characterBefore, characterAfter }) { return this._placeCharacterBeforeOrAfter({ characterBefore, characterAfter, type: 'after', }) } /** * Removes specific characters from the alphabet by their range. * @param {object} range - The Unicode range to remove characters from * @param {number} range.start - The starting Unicode codepoint * @param {number} range.end - The ending Unicode codepoint * @returns {Alphabet} - The same alphabet instance without the characters * from the specified range */ removeUnicodeRange({ start, end }) { this._characters = this._characters.filter( ({ codePoint }) => codePoint < start || codePoint > end, ) return this } /** * Sorts the alphabet by the sorting function provided. * @param {Function} sortingFunction - The sorting function to use * @returns {Alphabet} - The same alphabet instance sorted by the sorting function provided */ sortBy(sortingFunction) { this._characters.sort((a, b) => sortingFunction(a.value, b.value)) return this } /** * Removes specific characters from the alphabet. * @param {string|string[]} values - The characters to remove from the * alphabet * @returns {Alphabet} - The same alphabet instance without the specified * characters * @example * Alphabet.generateFrom('abcd') * .removeCharacters('dcc') * // Returns 'ab' */ removeCharacters(values) { this._characters = this._characters.filter( ({ value }) => !values.includes(value), ) return this } /** * Sorts the alphabet by the natural order of the characters using * `natural-orderby`. * @param {string} locale - The locale to use for sorting * @returns {Alphabet} - The same alphabet instance sorted by the natural * order of the characters */ sortByNaturalSort(locale) { let naturalCompare = naturalOrderby.compare({ locale, }) return this.sortBy((a, b) => naturalCompare(a, b)) } /** * Sorts the alphabet by the character code point. * @returns {Alphabet} - The same alphabet instance sorted by the character * code point */ sortByCharCodeAt() { return this.sortBy((a, b) => convertBooleanToSign.convertBooleanToSign( a.charCodeAt(0) > b.charCodeAt(0), ), ) } /** * Sorts the alphabet by the locale order of the characters. * @param {Intl.LocalesArgument} locales - The locales to use for sorting * @returns {Alphabet} - The same alphabet instance sorted by the locale * order of the characters */ sortByLocaleCompare(locales) { return this.sortBy((a, b) => a.localeCompare(b, locales)) } /** * Retrieves the characters from the alphabet. * @returns {string} The characters from the alphabet */ getCharacters() { return this._characters.map(({ value }) => value).join('') } /** * Reverses the alphabet. * @returns {Alphabet} - The same alphabet instance reversed * @example * Alphabet.generateFrom('ab') * .reverse() * // Returns 'ba' */ reverse() { this._characters.reverse() return this } _placeCharacterBeforeOrAfter({ characterBefore, characterAfter, type }) { let indexOfCharacterAfter = this._characters.findIndex( ({ value }) => value === characterAfter, ) let indexOfCharacterBefore = this._characters.findIndex( ({ value }) => value === characterBefore, ) if (indexOfCharacterAfter === -1) { throw new Error(`Character ${characterAfter} not found in alphabet`) } if (indexOfCharacterBefore === -1) { throw new Error(`Character ${characterBefore} not found in alphabet`) } if (indexOfCharacterBefore <= indexOfCharacterAfter) { return this } this._characters.splice( type === 'before' ? indexOfCharacterAfter : indexOfCharacterBefore + 1, 0, this._characters[ type === 'before' ? indexOfCharacterBefore : indexOfCharacterAfter ], ) this._characters.splice( type === 'before' ? indexOfCharacterBefore + 1 : indexOfCharacterAfter, 1, ) return this } _getCharactersWithCase() { return this._characters .map((character, index) => { if ( !character.uppercaseCharacterCodePoint && !character.lowercaseCharacterCodePoint ) { return null } return { character, index, } }) .filter(element => element !== null) } } exports.Alphabet = Alphabet