Skip to content

Commit

Permalink
Merge pull request #40 from ony3000/fix-nested-expression
Browse files Browse the repository at this point in the history
Improves formatting of nested expressions
  • Loading branch information
ony3000 authored Mar 8, 2024
2 parents be0bd8f + bfeb244 commit c65e52a
Show file tree
Hide file tree
Showing 11 changed files with 979 additions and 18 deletions.
11 changes: 10 additions & 1 deletion src/packages/core-parts/finder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ function filterAndSortClassNameNodes(
classNameRangeEnd <= keywordStartingRangeEnd,
),
)
.sort((former, latter) => latter.startLineIndex - former.startLineIndex);
.sort(
(
{ startLineIndex: formerStartLineIndex, range: [formerNodeRangeStart] },
{ startLineIndex: latterStartLineIndex, range: [latterNodeRangeStart] },
) =>
latterStartLineIndex - formerStartLineIndex || latterNodeRangeStart - formerNodeRangeStart,
);
}

export function findTargetClassNameNodes(
Expand Down Expand Up @@ -283,6 +289,9 @@ export function findTargetClassNameNodes(
} else if (classNameNode.type === ClassNameType.UTL) {
// eslint-disable-next-line no-param-reassign
classNameNode.type = ClassNameType.TLTO;
} else if (classNameNode.type === ClassNameType.TLPQ) {
// eslint-disable-next-line no-param-reassign
classNameNode.type = ClassNameType.TLPQTO;
}
}
});
Expand Down
182 changes: 165 additions & 17 deletions src/packages/core-parts/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { createHash } from 'node:crypto';

import {
findTargetClassNameNodes,
findTargetClassNameNodesForVue,
Expand Down Expand Up @@ -35,6 +37,7 @@ function getExtraIndentLevel(type: ClassNameType) {
ClassNameType.SLTO,
ClassNameType.TLSL,
ClassNameType.TLTO,
ClassNameType.TLPQTO,
].includes(type)
) {
return 1;
Expand All @@ -51,7 +54,7 @@ function getSomeKindOfQuotes(
// prettier-ignore
const baseQuote =
// eslint-disable-next-line no-nested-ternary
type === ClassNameType.TLPQ
type === ClassNameType.TLPQ || type === ClassNameType.TLPQTO
? '`'
: (
parser === 'vue' &&
Expand All @@ -65,6 +68,7 @@ function getSomeKindOfQuotes(
ClassNameType.TLSL,
ClassNameType.TLOP,
ClassNameType.TLTO,
ClassNameType.TLPQTO,
].includes(type)
? "'"
: '"'
Expand Down Expand Up @@ -97,6 +101,44 @@ function replaceSpacesAtBothEnds(className: string): [string, string, string] {
return [leadingSpace, replacedClassName, trailingSpace];
}

function sha1(input: string): string {
return createHash('sha1').update(input).digest('hex');
}

function freezeIndent(input: string): string {
const charCodeForUpperCaseAlpha = 913;
const greekPlaceholder = [...Array(16)].map((_, index) =>
String.fromCharCode(charCodeForUpperCaseAlpha + index),
);

const hash = sha1(input);
const prefix = hash
.slice(0, Math.min(input.length, hash.length))
.split('')
.map((hex) => greekPlaceholder[Number.parseInt(hex, 16)])
.join('');
const rest = PH.repeat(Math.max(0, input.length - hash.length));

return `${prefix}${rest}`;
}

function freezeString(input: string): string {
const charCodeForLowerCaseAlpha = 945;
const greekPlaceholder = [...Array(16)].map((_, index) =>
String.fromCharCode(charCodeForLowerCaseAlpha + index),
);

const hash = sha1(input);
const prefix = hash
.slice(0, Math.min(input.length, hash.length))
.split('')
.map((hex) => greekPlaceholder[Number.parseInt(hex, 16)])
.join('');
const rest = PH.repeat(Math.max(0, input.length - hash.length));

return `${prefix}${rest}`;
}

function replaceClassName({
formattedText,
indentUnit,
Expand All @@ -114,12 +156,21 @@ function replaceClassName({
format: (source: string, options?: any) => string;
targetClassNameTypes?: ClassNameType[];
}): string {
const mutableFormattedText = targetClassNameNodes.reduce(
(formattedPrevText, { type, range: [rangeStart, rangeEnd], startLineIndex }) => {
const freezer: { type: 'string' | 'indent'; from: string; to: string }[] = [];
const rangeCorrectionValues = [...Array(targetClassNameNodes.length)].map(() => 0);

const icedFormattedText = targetClassNameNodes.reduce(
(
formattedPrevText,
{ type, range: [rangeStart, rangeEnd], startLineIndex },
classNameNodeIndex,
) => {
if (targetClassNameTypes && !targetClassNameTypes.includes(type)) {
return formattedPrevText;
}

const correctedRangeEnd = rangeEnd - rangeCorrectionValues[classNameNodeIndex];

const isStartingPositionRelative = options.endingPosition !== 'absolute';
const isEndingPositionAbsolute = options.endingPosition !== 'relative';
const isOutputIdeal = isStartingPositionRelative && isEndingPositionAbsolute;
Expand All @@ -130,7 +181,7 @@ function replaceClassName({
? baseIndentLevel + extraIndentLevel
: 0;

const classNameBase = formattedPrevText.slice(rangeStart + 1, rangeEnd - 1);
const classNameBase = formattedPrevText.slice(rangeStart + 1, correctedRangeEnd - 1);

// preprocess (first)
const [leadingSpace, classNameWithoutSpacesAtBothEnds, trailingSpace] =
Expand Down Expand Up @@ -206,20 +257,64 @@ function replaceClassName({
isMultiLineClassName,
options.parser,
);

const rawIndent = indentUnit.repeat(multiLineIndentLevel);
const frozenIndent = freezeIndent(rawIndent);
const substitute = `${quoteStart}${classNameWithOriginalSpaces}${quoteEnd}`
.split(EOL)
.join(`${EOL}${indentUnit.repeat(multiLineIndentLevel)}`);
const sliceOffset = !isMultiLineClassName && type === ClassNameType.TLOP ? 1 : 0;
.map((raw) => {
const frozen = freezeString(raw);

freezer.push({
type: 'string',
from: frozen,
to: raw,
});

return frozen;
})
.join(`${EOL}${frozenIndent}`);

if (isStartingPositionRelative && isMultiLineClassName) {
freezer.push({
type: 'indent',
from: frozenIndent,
to: rawIndent,
});
}

return `${formattedPrevText.slice(
const sliceOffset = !isMultiLineClassName && type === ClassNameType.TLOP ? 1 : 0;
const classNamePartialWrappedText = `${formattedPrevText.slice(
0,
rangeStart - sliceOffset,
)}${substitute}${formattedPrevText.slice(rangeEnd + sliceOffset)}`;
)}${substitute}${formattedPrevText.slice(correctedRangeEnd + sliceOffset)}`;

rangeCorrectionValues.forEach((_, rangeCorrectionIndex, array) => {
if (rangeCorrectionIndex <= classNameNodeIndex) {
return;
}

const [nthNodeRangeStart, nthNodeRangeEnd] =
targetClassNameNodes[rangeCorrectionIndex].range;

if (nthNodeRangeStart < rangeStart && rangeEnd < nthNodeRangeEnd) {
// eslint-disable-next-line no-param-reassign
array[rangeCorrectionIndex] += correctedRangeEnd - rangeStart - substitute.length;
}
});

return classNamePartialWrappedText;
},
formattedText,
);

return mutableFormattedText;
return freezer.reduceRight(
(prevText, { type, from, to }) =>
type === 'indent'
? prevText.replace(new RegExp(`^\\s*${from}`, 'gm'), to)
: prevText.replace(from, to),
icedFormattedText,
);
}

export function parseLineByLineAndReplace({
Expand Down Expand Up @@ -289,14 +384,23 @@ async function replaceClassNameAsync({
format: (source: string, options?: any) => Promise<string>;
targetClassNameTypes?: ClassNameType[];
}): Promise<string> {
const mutableFormattedText = await targetClassNameNodes.reduce(
async (formattedPrevTextPromise, { type, range: [rangeStart, rangeEnd], startLineIndex }) => {
const freezer: { type: 'string' | 'indent'; from: string; to: string }[] = [];
const rangeCorrectionValues = [...Array(targetClassNameNodes.length)].map(() => 0);

const icedFormattedText = await targetClassNameNodes.reduce(
async (
formattedPrevTextPromise,
{ type, range: [rangeStart, rangeEnd], startLineIndex },
classNameNodeIndex,
) => {
if (targetClassNameTypes && !targetClassNameTypes.includes(type)) {
return formattedPrevTextPromise;
}

const formattedPrevText = await formattedPrevTextPromise;

const correctedRangeEnd = rangeEnd - rangeCorrectionValues[classNameNodeIndex];

const isStartingPositionRelative = options.endingPosition !== 'absolute';
const isEndingPositionAbsolute = options.endingPosition !== 'relative';
const isOutputIdeal = isStartingPositionRelative && isEndingPositionAbsolute;
Expand All @@ -307,7 +411,7 @@ async function replaceClassNameAsync({
? baseIndentLevel + extraIndentLevel
: 0;

const classNameBase = formattedPrevText.slice(rangeStart + 1, rangeEnd - 1);
const classNameBase = formattedPrevText.slice(rangeStart + 1, correctedRangeEnd - 1);

// preprocess (first)
const [leadingSpace, classNameWithoutSpacesAtBothEnds, trailingSpace] =
Expand Down Expand Up @@ -387,20 +491,64 @@ async function replaceClassNameAsync({
isMultiLineClassName,
options.parser,
);

const rawIndent = indentUnit.repeat(multiLineIndentLevel);
const frozenIndent = freezeIndent(rawIndent);
const substitute = `${quoteStart}${classNameWithOriginalSpaces}${quoteEnd}`
.split(EOL)
.join(`${EOL}${indentUnit.repeat(multiLineIndentLevel)}`);
const sliceOffset = !isMultiLineClassName && type === ClassNameType.TLOP ? 1 : 0;
.map((raw) => {
const frozen = freezeString(raw);

freezer.push({
type: 'string',
from: frozen,
to: raw,
});

return frozen;
})
.join(`${EOL}${frozenIndent}`);

if (isStartingPositionRelative && isMultiLineClassName) {
freezer.push({
type: 'indent',
from: frozenIndent,
to: rawIndent,
});
}

return `${formattedPrevText.slice(
const sliceOffset = !isMultiLineClassName && type === ClassNameType.TLOP ? 1 : 0;
const classNamePartialWrappedText = `${formattedPrevText.slice(
0,
rangeStart - sliceOffset,
)}${substitute}${formattedPrevText.slice(rangeEnd + sliceOffset)}`;
)}${substitute}${formattedPrevText.slice(correctedRangeEnd + sliceOffset)}`;

rangeCorrectionValues.forEach((_, rangeCorrectionIndex, array) => {
if (rangeCorrectionIndex <= classNameNodeIndex) {
return;
}

const [nthNodeRangeStart, nthNodeRangeEnd] =
targetClassNameNodes[rangeCorrectionIndex].range;

if (nthNodeRangeStart < rangeStart && rangeEnd < nthNodeRangeEnd) {
// eslint-disable-next-line no-param-reassign
array[rangeCorrectionIndex] += correctedRangeEnd - rangeStart - substitute.length;
}
});

return classNamePartialWrappedText;
},
Promise.resolve(formattedText),
);

return mutableFormattedText;
return freezer.reduceRight(
(prevText, { type, from, to }) =>
type === 'indent'
? prevText.replace(new RegExp(`^\\s*${from}`, 'gm'), to)
: prevText.replace(from, to),
icedFormattedText,
);
}

export async function parseLineByLineAndReplaceAsync({
Expand Down
4 changes: 4 additions & 0 deletions src/packages/core-parts/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export enum ClassNameType {
* Template literal that preserve quotes
*/
TLPQ,
/**
* Template literal that preserve quotes (in ternary operator)
*/
TLPQTO,
/**
* Unknown string literal
*/
Expand Down
Loading

0 comments on commit c65e52a

Please sign in to comment.