From c58c2cde93458aef3c9a96e2d679d3def8da36a4 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Wed, 23 Oct 2024 22:42:12 +0900 Subject: [PATCH 01/20] feat: implement experimental logic --- src/packages/core-parts/experimental.ts | 247 ++++++++++++++++++++++++ src/packages/core-parts/index.ts | 9 + 2 files changed, 256 insertions(+) create mode 100644 src/packages/core-parts/experimental.ts diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts new file mode 100644 index 0000000..3589d2a --- /dev/null +++ b/src/packages/core-parts/experimental.ts @@ -0,0 +1,247 @@ +import type { ClassNameNode } from './shared'; +import { EOL, SPACE } from './shared'; + +type LinePart = { + type: string; + body: string; +}; + +type LineNode = { + indentLevel: number; + parts: LinePart[]; +}; + +const inspectOptions: any = { depth: null }; + +function parseLineByLine( + formattedText: string, + indentUnit: string, + targetClassNameNodes: ClassNameNode[], +): LineNode[] { + const formattedLines = formattedText.split(EOL); + let rangeStartOfLine = 0; + let rangeEndOfLine: number; + + const parsedLineNodes: LineNode[] = []; + let numberOfLinesToSkip = 0; + + for (let currentLineIndex = 0; currentLineIndex < formattedLines.length; currentLineIndex += 1) { + const line = formattedLines[currentLineIndex]; + + const indentMatchResult = line.match(new RegExp(`^(${indentUnit})*`)); + const indentLevel = indentMatchResult![0].length / indentUnit.length; + const offset = indentUnit.length * indentLevel; + const parts: LinePart[] = []; + + rangeEndOfLine = rangeStartOfLine + line.length; + + if (numberOfLinesToSkip > 0) { + // nothing to do + numberOfLinesToSkip -= 1; + } else { + const classNameNodesStartingFromCurrentLine = targetClassNameNodes.filter( + ({ startLineIndex }) => startLineIndex === currentLineIndex, + ); + + let temporaryRangeEnd = rangeEndOfLine; + + for (let index = 0; index < classNameNodesStartingFromCurrentLine.length; index += 1) { + const classNameNode = classNameNodesStartingFromCurrentLine[index]; + const [rangeStartOfClassName, rangeEndOfClassName] = classNameNode.range; + + parts.push({ + type: 'Text', + body: formattedText.slice(rangeEndOfClassName, temporaryRangeEnd), + }); + parts.push({ + type: 'ClosingDelimiter', + body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName), + }); + + const classNameLiteralOrExpression = formattedText.slice( + rangeStartOfClassName + 1, + rangeEndOfClassName - 1, + ); + parts.push({ + type: classNameNode.type, + body: classNameLiteralOrExpression, + }); + numberOfLinesToSkip += classNameLiteralOrExpression.split(EOL).length - 1; + + parts.push({ + type: 'OpeningDelimiter', + body: formattedText.slice(rangeStartOfClassName, rangeStartOfClassName + 1), + }); + + temporaryRangeEnd = rangeStartOfClassName; + } + parts.push({ + type: 'Text', + body: formattedText.slice(rangeStartOfLine, temporaryRangeEnd).slice(offset), + }); + parts.reverse(); + + if (parts.length > 1 && parts[0].body === '') { + parts.shift(); + } + if (parts.length > 1 && parts[parts.length - 1].body === '') { + parts.pop(); + } + + parsedLineNodes.push({ + indentLevel, + parts, + }); + } + + rangeStartOfLine = rangeEndOfLine + EOL.length; + } + + return parsedLineNodes; +} + +function formatClassName(source: string, options: ResolvedOptions): string { + const lines: string[] = []; + let remainder = `${source}${SPACE}`.replace(/[\s]+/g, SPACE); + + while (remainder.length) { + const chunk = remainder.slice(0, options.printWidth + 1); + const lastSpaceIndexInChunk = chunk.lastIndexOf(SPACE); + let lineCandidate = remainder.slice(0, lastSpaceIndexInChunk); + + if (lastSpaceIndexInChunk === -1) { + const firstSpaceIndex = remainder.indexOf(SPACE); + + lineCandidate = remainder.slice(0, firstSpaceIndex); + + if (firstSpaceIndex === -1) { + lineCandidate = remainder; + remainder = ''; + } else { + remainder = remainder.slice(firstSpaceIndex).trimStart(); + } + } else { + remainder = remainder.slice(lastSpaceIndexInChunk).trimStart(); + } + + lines.push(lineCandidate); + } + + if (options.debugFlag) { + // eslint-disable-next-line no-console + console.dir(lines, inspectOptions); + } + + return lines.join(EOL); +} + +function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedOptions): void { + for (let lineIndex = lineNodes.length - 1; lineIndex >= 0; lineIndex -= 1) { + const { indentLevel, parts } = lineNodes[lineIndex]; + const temporaryLineNodes: LineNode[] = []; + + let temporaryPartIndex = parts.length; + + for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) { + const currentPart = parts[partIndex]; + + if (currentPart.type === 'attribute') { + const slicedLineNodes: LineNode[] = []; + const classNameBase = currentPart.body.trim(); + const formattedClassName = formatClassName(classNameBase, options); + if (options.debugFlag) { + // eslint-disable-next-line no-console + console.dir(formattedClassName, inspectOptions); + } + const isMultiLineClassName = formattedClassName.split(EOL).length > 1; + + if (isMultiLineClassName) { + slicedLineNodes.push( + ...formattedClassName.split(EOL).map((line) => ({ + indentLevel, + parts: [ + { + type: 'Text', + body: line, + }, + ], + })), + ); + + slicedLineNodes[slicedLineNodes.length - 1].parts.push( + ...parts.splice(partIndex + 1, parts.length - (partIndex + 1)), + ); + + parts.splice(partIndex, 1, ...slicedLineNodes.splice(0, 1)[0].parts); + + slicedLineNodes.forEach((lineNode) => { + // eslint-disable-next-line no-param-reassign + lineNode.indentLevel += 1; + }); + + slicedLineNodes.reverse(); + + temporaryLineNodes.push(...slicedLineNodes); + temporaryPartIndex = partIndex + 1; + } else { + currentPart.type = 'Text'; + currentPart.body = formattedClassName; + } + } + } + + temporaryLineNodes.push({ + indentLevel, + parts: parts.slice(0, temporaryPartIndex), + }); + temporaryLineNodes.reverse(); + + lineNodes.splice( + lineIndex, + 1, + ...temporaryLineNodes.filter((lineNode) => lineNode.parts.length), + ); + } +} + +function assembleLine(lineNodes: LineNode[], indentUnit: string): string { + return lineNodes + .map( + ({ indentLevel, parts }) => + `${indentUnit.repeat(indentLevel)}${parts.map(({ body }) => body).join('')}`, + ) + .join(EOL); +} + +/** + * Parse and assemble like `prettier-plugin-brace-style` + */ +export function parseAndAssembleLikePpbs( + formattedText: string, + indentUnit: string, + targetClassNameNodes: ClassNameNode[], + options: ResolvedOptions, +): string { + if (options.debugFlag) { + // eslint-disable-next-line no-console + console.dir(formattedText, inspectOptions); + // eslint-disable-next-line no-console + console.dir(targetClassNameNodes, inspectOptions); + } + + const lineNodes = parseLineByLine(formattedText, indentUnit, targetClassNameNodes); + + if (options.debugFlag) { + // eslint-disable-next-line no-console + console.dir(lineNodes, inspectOptions); + } + + transformClassNameIfNecessary(lineNodes, options); + + if (options.debugFlag) { + // eslint-disable-next-line no-console + console.dir(lineNodes, inspectOptions); + } + + return assembleLine(lineNodes, indentUnit); +} diff --git a/src/packages/core-parts/index.ts b/src/packages/core-parts/index.ts index 7907f72..624e071 100644 --- a/src/packages/core-parts/index.ts +++ b/src/packages/core-parts/index.ts @@ -1,5 +1,6 @@ import { createHash } from 'node:crypto'; +import { parseAndAssembleLikePpbs } from './experimental'; import { findTargetClassNameNodes, findTargetClassNameNodesForHtml, @@ -463,6 +464,10 @@ export function parseLineByLineAndReplace({ } } + if (options.experimentalOptimization && options.parser === 'babel') { + return parseAndAssembleLikePpbs(formattedText, indentUnit, targetClassNameNodes, options); + } + const lineNodes = parseLineByLine(formattedText, indentUnit); const classNameWrappedText = replaceClassName({ @@ -795,6 +800,10 @@ export async function parseLineByLineAndReplaceAsync({ } } + if (options.experimentalOptimization && options.parser === 'babel') { + return parseAndAssembleLikePpbs(formattedText, indentUnit, targetClassNameNodes, options); + } + const lineNodes = parseLineByLine(formattedText, indentUnit); const classNameWrappedText = await replaceClassNameAsync({ From 44e3127f5977851e94617736b846149ab84c4ae4 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 25 Oct 2024 08:25:51 +0900 Subject: [PATCH 02/20] feat: update experimental logic --- src/packages/core-parts/experimental.ts | 51 ++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 3589d2a..e5a54d6 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,5 +1,5 @@ import type { ClassNameNode } from './shared'; -import { EOL, SPACE } from './shared'; +import { EOL, PH, SPACE } from './shared'; type LinePart = { type: string; @@ -100,12 +100,12 @@ function parseLineByLine( return parsedLineNodes; } -function formatClassName(source: string, options: ResolvedOptions): string { +function formatClassName(source: string, printWidth: number): string { const lines: string[] = []; let remainder = `${source}${SPACE}`.replace(/[\s]+/g, SPACE); while (remainder.length) { - const chunk = remainder.slice(0, options.printWidth + 1); + const chunk = remainder.slice(0, Math.max(0, printWidth) + 1); const lastSpaceIndexInChunk = chunk.lastIndexOf(SPACE); let lineCandidate = remainder.slice(0, lastSpaceIndexInChunk); @@ -127,15 +127,14 @@ function formatClassName(source: string, options: ResolvedOptions): string { lines.push(lineCandidate); } - if (options.debugFlag) { - // eslint-disable-next-line no-console - console.dir(lines, inspectOptions); - } - return lines.join(EOL); } function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedOptions): void { + const isStartingPositionRelative = options.endingPosition !== 'absolute'; + const isEndingPositionAbsolute = options.endingPosition !== 'relative'; + const isOutputIdeal = isStartingPositionRelative && isEndingPositionAbsolute; + for (let lineIndex = lineNodes.length - 1; lineIndex >= 0; lineIndex -= 1) { const { indentLevel, parts } = lineNodes[lineIndex]; const temporaryLineNodes: LineNode[] = []; @@ -147,15 +146,36 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO if (currentPart.type === 'attribute') { const slicedLineNodes: LineNode[] = []; - const classNameBase = currentPart.body.trim(); - const formattedClassName = formatClassName(classNameBase, options); + const leadingText = isEndingPositionAbsolute + ? `${SPACE.repeat(options.tabWidth * indentLevel)}${parts + .slice(0, partIndex) + .map(({ body }) => body) + .join('')}` + : ''; + const classNameBase = `${PH.repeat(leadingText.length)}${currentPart.body.trim()}`; + let formattedClassName = formatClassName(classNameBase, options.printWidth).slice( + leadingText.length, + ); if (options.debugFlag) { // eslint-disable-next-line no-console console.dir(formattedClassName, inspectOptions); } - const isMultiLineClassName = formattedClassName.split(EOL).length > 1; + const lines = formattedClassName.split(EOL); + const isMultiLineClassName = lines.length > 1; if (isMultiLineClassName) { + if (isOutputIdeal) { + const multiLineIndentLevel = indentLevel + 1; + + formattedClassName = [ + lines[0], + formatClassName( + lines.slice(1).join(EOL), + options.printWidth - options.tabWidth * multiLineIndentLevel, + ), + ].join(EOL); + } + slicedLineNodes.push( ...formattedClassName.split(EOL).map((line) => ({ indentLevel, @@ -175,8 +195,13 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO parts.splice(partIndex, 1, ...slicedLineNodes.splice(0, 1)[0].parts); slicedLineNodes.forEach((lineNode) => { - // eslint-disable-next-line no-param-reassign - lineNode.indentLevel += 1; + if (isStartingPositionRelative) { + // eslint-disable-next-line no-param-reassign + lineNode.indentLevel += 1; + } else { + // eslint-disable-next-line no-param-reassign + lineNode.indentLevel = 0; + } }); slicedLineNodes.reverse(); From f4925cbd96deeda6eb811bac0368aa6a51bba358 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 25 Oct 2024 23:02:53 +0900 Subject: [PATCH 03/20] feat: update experimental logic --- src/packages/core-parts/experimental.ts | 110 ++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 7 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index e5a54d6..ef586f1 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,5 +1,5 @@ import type { ClassNameNode } from './shared'; -import { EOL, PH, SPACE } from './shared'; +import { EOL, PH, SPACE, BACKTICK } from './shared'; type LinePart = { type: string; @@ -49,24 +49,38 @@ function parseLineByLine( const classNameNode = classNameNodesStartingFromCurrentLine[index]; const [rangeStartOfClassName, rangeEndOfClassName] = classNameNode.range; + const classNameLiteralOrExpression = formattedText.slice( + rangeStartOfClassName + 1, + rangeEndOfClassName - 1, + ); + const numberOfLinesOfClassName = classNameLiteralOrExpression.split(EOL).length; + const rangeEndOfLineWhereClassNameEnds = formattedLines + .slice(0, currentLineIndex + numberOfLinesOfClassName) + .reduce( + (totalTextLength, text, textIndex) => + totalTextLength + (textIndex > 0 ? EOL.length : 0) + text.length, + 0, + ); + parts.push({ type: 'Text', - body: formattedText.slice(rangeEndOfClassName, temporaryRangeEnd), + body: formattedText.slice( + rangeEndOfClassName, + rangeEndOfClassName < rangeEndOfLineWhereClassNameEnds + ? rangeEndOfLineWhereClassNameEnds + : temporaryRangeEnd, + ), }); parts.push({ type: 'ClosingDelimiter', body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName), }); - const classNameLiteralOrExpression = formattedText.slice( - rangeStartOfClassName + 1, - rangeEndOfClassName - 1, - ); parts.push({ type: classNameNode.type, body: classNameLiteralOrExpression, }); - numberOfLinesToSkip += classNameLiteralOrExpression.split(EOL).length - 1; + numberOfLinesToSkip += numberOfLinesOfClassName - 1; parts.push({ type: 'OpeningDelimiter', @@ -153,6 +167,7 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO .join('')}` : ''; const classNameBase = `${PH.repeat(leadingText.length)}${currentPart.body.trim()}`; + let formattedClassName = formatClassName(classNameBase, options.printWidth).slice( leadingText.length, ); @@ -206,6 +221,87 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO slicedLineNodes.reverse(); + temporaryLineNodes.push(...slicedLineNodes); + temporaryPartIndex = partIndex + 1; + } else { + currentPart.type = 'Text'; + currentPart.body = formattedClassName; + } + } else if (currentPart.type === 'expression') { + const slicedLineNodes: LineNode[] = []; + const leadingText = isEndingPositionAbsolute + ? `${SPACE.repeat(options.tabWidth * indentLevel)}${parts + .slice(0, partIndex) + .map(({ body }) => body) + .join('')}` + : ''; + const hasLeadingSpace = currentPart.body !== currentPart.body.trimStart(); + const hasTrailingSpace = currentPart.body !== currentPart.body.trimEnd(); + const classNameBase = `${PH.repeat(leadingText.length)}${ + hasLeadingSpace ? SPACE : '' + }${currentPart.body.trim().replace(/\\\n/g, '')}`; + + let formattedClassName = `${formatClassName(classNameBase, options.printWidth).slice( + leadingText.length, + )}${hasTrailingSpace ? SPACE : ''}`; + if (options.debugFlag) { + // eslint-disable-next-line no-console + console.dir(formattedClassName, inspectOptions); + } + const lines = formattedClassName.split(EOL); + const isMultiLineClassName = lines.length > 1; + + if (isMultiLineClassName) { + if (isOutputIdeal) { + const multiLineIndentLevel = indentLevel + 1; + + formattedClassName = [ + lines[0], + `${formatClassName( + lines.slice(1).join(EOL), + options.printWidth - options.tabWidth * multiLineIndentLevel, + )}${hasTrailingSpace ? SPACE : ''}`, + ].join(EOL); + } + + slicedLineNodes.push( + ...formattedClassName.split(EOL).map((line) => ({ + indentLevel, + parts: [ + { + type: 'Text', + body: line, + }, + ], + })), + ); + + const isTheFirstLineOnTheSameLineAsTheAttributeName = + parts.slice(0, Math.max(0, partIndex - 1)).length > 0; + + parts[partIndex - 1].body = BACKTICK; + parts[partIndex + 1].body = BACKTICK; + + slicedLineNodes[slicedLineNodes.length - 1].parts.push( + ...parts.splice(partIndex + 1, parts.length - (partIndex + 1)), + ); + + parts.splice(partIndex, 1, ...slicedLineNodes.splice(0, 1)[0].parts); + + slicedLineNodes.forEach((lineNode) => { + if (isStartingPositionRelative) { + if (isTheFirstLineOnTheSameLineAsTheAttributeName) { + // eslint-disable-next-line no-param-reassign + lineNode.indentLevel += 1; + } + } else { + // eslint-disable-next-line no-param-reassign + lineNode.indentLevel = 0; + } + }); + + slicedLineNodes.reverse(); + temporaryLineNodes.push(...slicedLineNodes); temporaryPartIndex = partIndex + 1; } else { From a252b18329b3b964655f324ed21f4b1920724b96 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sat, 26 Oct 2024 11:01:51 +0900 Subject: [PATCH 04/20] feat: update experimental logic --- src/packages/core-parts/experimental.ts | 54 ++++++++++++++++++------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index ef586f1..af17e27 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,9 +1,10 @@ -import type { ClassNameNode } from './shared'; +import type { ExpressionNode, ClassNameNode } from './shared'; import { EOL, PH, SPACE, BACKTICK } from './shared'; type LinePart = { type: string; body: string; + props?: Omit; }; type LineNode = { @@ -47,7 +48,13 @@ function parseLineByLine( for (let index = 0; index < classNameNodesStartingFromCurrentLine.length; index += 1) { const classNameNode = classNameNodesStartingFromCurrentLine[index]; - const [rangeStartOfClassName, rangeEndOfClassName] = classNameNode.range; + const { + range, + startLineIndex, // not in use + ...restWithType + } = classNameNode; + const { type, ...restWithoutType } = restWithType; + const [rangeStartOfClassName, rangeEndOfClassName] = range; const classNameLiteralOrExpression = formattedText.slice( rangeStartOfClassName + 1, @@ -62,32 +69,43 @@ function parseLineByLine( 0, ); + const delimiterOffset = + restWithType.type === 'expression' && + restWithType.isItAnObjectProperty && + restWithType.delimiterType === 'backtick' + ? 1 + : 0; + parts.push({ type: 'Text', body: formattedText.slice( - rangeEndOfClassName, - rangeEndOfClassName < rangeEndOfLineWhereClassNameEnds + rangeEndOfClassName + delimiterOffset, + rangeEndOfClassName + delimiterOffset < rangeEndOfLineWhereClassNameEnds ? rangeEndOfLineWhereClassNameEnds : temporaryRangeEnd, ), }); parts.push({ type: 'ClosingDelimiter', - body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName), + body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset), }); parts.push({ - type: classNameNode.type, + type, body: classNameLiteralOrExpression, + props: restWithoutType, }); numberOfLinesToSkip += numberOfLinesOfClassName - 1; parts.push({ type: 'OpeningDelimiter', - body: formattedText.slice(rangeStartOfClassName, rangeStartOfClassName + 1), + body: formattedText.slice( + rangeStartOfClassName - delimiterOffset, + rangeStartOfClassName + 1, + ), }); - temporaryRangeEnd = rangeStartOfClassName; + temporaryRangeEnd = rangeStartOfClassName - delimiterOffset; } parts.push({ type: 'Text', @@ -252,8 +270,17 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO const isMultiLineClassName = lines.length > 1; if (isMultiLineClassName) { + const props = currentPart.props as Omit< + ExpressionNode, + 'type' | 'range' | 'startLineIndex' + >; + if (isOutputIdeal) { - const multiLineIndentLevel = indentLevel + 1; + const multiLineIndentLevel = + props.isTheFirstLineOnTheSameLineAsTheAttributeName || + props.isItAnOperandOfTernaryOperator + ? indentLevel + 1 + : indentLevel; formattedClassName = [ lines[0], @@ -276,11 +303,10 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO })), ); - const isTheFirstLineOnTheSameLineAsTheAttributeName = - parts.slice(0, Math.max(0, partIndex - 1)).length > 0; + const areNeededBrackets = isMultiLineClassName && props.isItAnObjectProperty; - parts[partIndex - 1].body = BACKTICK; - parts[partIndex + 1].body = BACKTICK; + parts[partIndex - 1].body = `${areNeededBrackets ? '[' : ''}${BACKTICK}`; + parts[partIndex + 1].body = `${BACKTICK}${areNeededBrackets ? ']' : ''}`; slicedLineNodes[slicedLineNodes.length - 1].parts.push( ...parts.splice(partIndex + 1, parts.length - (partIndex + 1)), @@ -290,7 +316,7 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO slicedLineNodes.forEach((lineNode) => { if (isStartingPositionRelative) { - if (isTheFirstLineOnTheSameLineAsTheAttributeName) { + if (props.isTheFirstLineOnTheSameLineAsTheAttributeName) { // eslint-disable-next-line no-param-reassign lineNode.indentLevel += 1; } From 7c1de84c179b2987dc28ae4029ef89b97432d1dd Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sat, 26 Oct 2024 23:49:06 +0900 Subject: [PATCH 05/20] feat: update experimental logic --- src/packages/core-parts/experimental.ts | 138 +++++++++++++----------- 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index af17e27..aedf087 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,4 +1,4 @@ -import type { ExpressionNode, ClassNameNode } from './shared'; +import type { Dict, NodeRange, ExpressionNode, ClassNameNode } from './shared'; import { EOL, PH, SPACE, BACKTICK } from './shared'; type LinePart = { @@ -14,60 +14,63 @@ type LineNode = { const inspectOptions: any = { depth: null }; -function parseLineByLine( +function parseLineByLineFromBottomToTop( formattedText: string, indentUnit: string, targetClassNameNodes: ClassNameNode[], ): LineNode[] { const formattedLines = formattedText.split(EOL); - let rangeStartOfLine = 0; - let rangeEndOfLine: number; + let rangeStartOfLine: number; + let rangeEndOfLine = formattedText.length; + const rangePerLine: Dict = {}; const parsedLineNodes: LineNode[] = []; - let numberOfLinesToSkip = 0; - for (let currentLineIndex = 0; currentLineIndex < formattedLines.length; currentLineIndex += 1) { + for ( + let currentLineIndex = formattedLines.length - 1; + currentLineIndex >= 0; + currentLineIndex -= 1 + ) { const line = formattedLines[currentLineIndex]; const indentMatchResult = line.match(new RegExp(`^(${indentUnit})*`)); const indentLevel = indentMatchResult![0].length / indentUnit.length; - const offset = indentUnit.length * indentLevel; + const indentOffset = indentUnit.length * indentLevel; const parts: LinePart[] = []; - rangeEndOfLine = rangeStartOfLine + line.length; + rangeStartOfLine = rangeEndOfLine - line.length; + rangePerLine[currentLineIndex] = [rangeStartOfLine, rangeEndOfLine]; - if (numberOfLinesToSkip > 0) { - // nothing to do - numberOfLinesToSkip -= 1; - } else { - const classNameNodesStartingFromCurrentLine = targetClassNameNodes.filter( - ({ startLineIndex }) => startLineIndex === currentLineIndex, + const classNameNodesStartingFromCurrentLine = targetClassNameNodes.filter( + ({ startLineIndex }) => startLineIndex === currentLineIndex, + ); + + let rangeEndOfPart = rangeEndOfLine; + + for ( + let nodeIndex = 0; + nodeIndex < classNameNodesStartingFromCurrentLine.length; + nodeIndex += 1 + ) { + const classNameNode = classNameNodesStartingFromCurrentLine[nodeIndex]; + const { + range, + startLineIndex, // not in use + ...restWithType + } = classNameNode; + const { type, ...restWithoutType } = restWithType; + const [rangeStartOfClassName, rangeEndOfClassName] = range; + + const classNameWithoutDelimiter = formattedText.slice( + rangeStartOfClassName + 1, + rangeEndOfClassName - 1, ); + const numberOfLinesOfClassName = classNameWithoutDelimiter.split(EOL).length; + const numberOfLinesToSkip = numberOfLinesOfClassName - 1; + const rangeOfLineWhereClassNameEnds = rangePerLine[currentLineIndex + numberOfLinesToSkip]; - let temporaryRangeEnd = rangeEndOfLine; - - for (let index = 0; index < classNameNodesStartingFromCurrentLine.length; index += 1) { - const classNameNode = classNameNodesStartingFromCurrentLine[index]; - const { - range, - startLineIndex, // not in use - ...restWithType - } = classNameNode; - const { type, ...restWithoutType } = restWithType; - const [rangeStartOfClassName, rangeEndOfClassName] = range; - - const classNameLiteralOrExpression = formattedText.slice( - rangeStartOfClassName + 1, - rangeEndOfClassName - 1, - ); - const numberOfLinesOfClassName = classNameLiteralOrExpression.split(EOL).length; - const rangeEndOfLineWhereClassNameEnds = formattedLines - .slice(0, currentLineIndex + numberOfLinesOfClassName) - .reduce( - (totalTextLength, text, textIndex) => - totalTextLength + (textIndex > 0 ? EOL.length : 0) + text.length, - 0, - ); + if (rangeOfLineWhereClassNameEnds) { + const [, rangeEndOfLineWhereClassNameEnds] = rangeOfLineWhereClassNameEnds; const delimiterOffset = restWithType.type === 'expression' && @@ -82,21 +85,18 @@ function parseLineByLine( rangeEndOfClassName + delimiterOffset, rangeEndOfClassName + delimiterOffset < rangeEndOfLineWhereClassNameEnds ? rangeEndOfLineWhereClassNameEnds - : temporaryRangeEnd, + : rangeEndOfPart, ), }); parts.push({ type: 'ClosingDelimiter', body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset), }); - parts.push({ type, - body: classNameLiteralOrExpression, + body: classNameWithoutDelimiter, props: restWithoutType, }); - numberOfLinesToSkip += numberOfLinesOfClassName - 1; - parts.push({ type: 'OpeningDelimiter', body: formattedText.slice( @@ -105,30 +105,37 @@ function parseLineByLine( ), }); - temporaryRangeEnd = rangeStartOfClassName - delimiterOffset; + rangeEndOfPart = rangeStartOfClassName - delimiterOffset; } - parts.push({ - type: 'Text', - body: formattedText.slice(rangeStartOfLine, temporaryRangeEnd).slice(offset), - }); - parts.reverse(); - - if (parts.length > 1 && parts[0].body === '') { - parts.shift(); - } - if (parts.length > 1 && parts[parts.length - 1].body === '') { - parts.pop(); + + if (numberOfLinesToSkip) { + parsedLineNodes.splice(parsedLineNodes.length - numberOfLinesToSkip, numberOfLinesToSkip); } + } + + parts.push({ + type: 'Text', + body: formattedText.slice(rangeStartOfLine, rangeEndOfPart).slice(indentOffset), + }); + parts.reverse(); - parsedLineNodes.push({ - indentLevel, - parts, - }); + if (parts.length > 1 && parts[0].body === '') { + parts.shift(); + } + if (parts.length > 1 && parts[parts.length - 1].body === '') { + parts.pop(); } - rangeStartOfLine = rangeEndOfLine + EOL.length; + parsedLineNodes.push({ + indentLevel, + parts, + }); + + rangeEndOfLine = rangeStartOfLine - EOL.length; } + parsedLineNodes.reverse(); + return parsedLineNodes; } @@ -316,7 +323,10 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO slicedLineNodes.forEach((lineNode) => { if (isStartingPositionRelative) { - if (props.isTheFirstLineOnTheSameLineAsTheAttributeName) { + if ( + props.isTheFirstLineOnTheSameLineAsTheAttributeName || + props.isItAnOperandOfTernaryOperator + ) { // eslint-disable-next-line no-param-reassign lineNode.indentLevel += 1; } @@ -376,7 +386,11 @@ export function parseAndAssembleLikePpbs( console.dir(targetClassNameNodes, inspectOptions); } - const lineNodes = parseLineByLine(formattedText, indentUnit, targetClassNameNodes); + const lineNodes = parseLineByLineFromBottomToTop( + formattedText, + indentUnit, + targetClassNameNodes.filter(({ type }) => type !== 'ternary'), + ); if (options.debugFlag) { // eslint-disable-next-line no-console From b6d8505017d25c75e6972b29e3b32345545a9933 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sun, 27 Oct 2024 00:23:21 +0900 Subject: [PATCH 06/20] feat: update experimental logic --- src/packages/core-parts/experimental.ts | 62 ++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index aedf087..9a86e91 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,5 +1,5 @@ import type { Dict, NodeRange, ExpressionNode, ClassNameNode } from './shared'; -import { EOL, PH, SPACE, BACKTICK } from './shared'; +import { EOL, PH, SPACE, SINGLE_QUOTE, DOUBLE_QUOTE, BACKTICK } from './shared'; type LinePart = { type: string; @@ -253,6 +253,11 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO currentPart.body = formattedClassName; } } else if (currentPart.type === 'expression') { + const props = currentPart.props as Omit< + ExpressionNode, + 'type' | 'range' | 'startLineIndex' + >; + const slicedLineNodes: LineNode[] = []; const leadingText = isEndingPositionAbsolute ? `${SPACE.repeat(options.tabWidth * indentLevel)}${parts @@ -277,11 +282,6 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO const isMultiLineClassName = lines.length > 1; if (isMultiLineClassName) { - const props = currentPart.props as Omit< - ExpressionNode, - 'type' | 'range' | 'startLineIndex' - >; - if (isOutputIdeal) { const multiLineIndentLevel = props.isTheFirstLineOnTheSameLineAsTheAttributeName || @@ -298,6 +298,10 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO ].join(EOL); } + if (props.delimiterType !== 'backtick' && props.hasBacktick) { + formattedClassName = formattedClassName.replace(/`/g, `\\${BACKTICK}`); + } + slicedLineNodes.push( ...formattedClassName.split(EOL).map((line) => ({ indentLevel, @@ -310,7 +314,7 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO })), ); - const areNeededBrackets = isMultiLineClassName && props.isItAnObjectProperty; + const areNeededBrackets = props.isItAnObjectProperty; parts[partIndex - 1].body = `${areNeededBrackets ? '[' : ''}${BACKTICK}`; parts[partIndex + 1].body = `${BACKTICK}${areNeededBrackets ? ']' : ''}`; @@ -343,6 +347,50 @@ function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedO } else { currentPart.type = 'Text'; currentPart.body = formattedClassName; + + let baseDelimiter = DOUBLE_QUOTE; + + if (props.shouldKeepDelimiter) { + if (props.delimiterType === 'backtick') { + baseDelimiter = BACKTICK; + } else if (props.delimiterType === 'single-quote') { + baseDelimiter = SINGLE_QUOTE; + } else { + // baseDelimiter = DOUBLE_QUOTE; + } + } else if (props.isItInVueTemplate) { + baseDelimiter = SINGLE_QUOTE; + } else if (options.singleQuote) { + if (props.hasSingleQuote) { + // baseDelimiter = DOUBLE_QUOTE; + } else { + baseDelimiter = SINGLE_QUOTE; + } + } else if (!options.singleQuote) { + if (props.hasDoubleQuote) { + baseDelimiter = SINGLE_QUOTE; + } else { + // baseDelimiter = DOUBLE_QUOTE; + } + } + + parts[partIndex - 1].body = baseDelimiter; + parts[partIndex + 1].body = baseDelimiter; + + if (baseDelimiter === SINGLE_QUOTE) { + if (props.delimiterType !== 'single-quote' && props.hasSingleQuote) { + currentPart.body = formattedClassName.replace(/'/g, `\\${SINGLE_QUOTE}`); + } + } else if (baseDelimiter === DOUBLE_QUOTE) { + if (props.delimiterType !== 'double-quote' && props.hasDoubleQuote) { + currentPart.body = formattedClassName.replace(/"/g, `\\${DOUBLE_QUOTE}`); + } + } else { + // eslint-disable-next-line no-lonely-if + if (props.delimiterType !== 'backtick' && props.hasBacktick) { + currentPart.body = formattedClassName.replace(/`/g, `\\${BACKTICK}`); + } + } } } } From e68047db1fc81769fdf6127e4f6d7bd494023f75 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Tue, 29 Oct 2024 16:30:01 +0900 Subject: [PATCH 07/20] feat: introduce linear parsing logic to better parse nested expressions --- src/packages/core-parts/experimental.ts | 259 +++++++++++++++++++++++- src/packages/core-parts/index.ts | 6 +- src/packages/core-parts/shared.ts | 2 +- 3 files changed, 262 insertions(+), 5 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 9a86e91..ed7ceb9 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,4 +1,12 @@ -import type { Dict, NodeRange, ExpressionNode, ClassNameNode } from './shared'; +import { createHash } from 'node:crypto'; + +import type { + Dict, + NodeRange, + ExpressionNode, + TernaryExpressionNode, + ClassNameNode, +} from './shared'; import { EOL, PH, SPACE, SINGLE_QUOTE, DOUBLE_QUOTE, BACKTICK } from './shared'; type LinePart = { @@ -14,6 +22,27 @@ type LineNode = { const inspectOptions: any = { depth: null }; +function sha1(input: string): string { + return createHash('sha1').update(input).digest('hex'); +} + +function freezeClassName(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 parseLineByLineFromBottomToTop( formattedText: string, indentUnit: string, @@ -420,6 +449,8 @@ function assembleLine(lineNodes: LineNode[], indentUnit: string): string { /** * Parse and assemble like `prettier-plugin-brace-style` + * + * @deprecated */ export function parseAndAssembleLikePpbs( formattedText: string, @@ -454,3 +485,229 @@ export function parseAndAssembleLikePpbs( return assembleLine(lineNodes, indentUnit); } + +// ---------------------------------------------------------------- + +type NonTernaryNode = Exclude; + +type StructuredClassNameNode = NonTernaryNode & { + parent?: StructuredClassNameNode; + children: StructuredClassNameNode[]; +}; + +type TextToken = { + type: string; + range: NodeRange; + body: string; + frozenClassName?: string; + children?: TextToken[]; + props?: Omit; +}; + +function structuringClassNameNodes( + targetClassNameNodes: NonTernaryNode[], +): StructuredClassNameNode[] { + const sortedStructuredClassNameNodes: StructuredClassNameNode[] = targetClassNameNodes + .map((classNameNode) => ({ + ...classNameNode, + children: [], + })) + .sort((former, latter) => { + const [rangeStartOfFormer, rangeEndOfFormer] = former.range; // [a1, a2] + const [rangeStartOfLatter, rangeEndOfLatter] = latter.range; // [b1, b2] + + // a1 < a2 < b1 < b2 + if (rangeStartOfFormer < rangeStartOfLatter && rangeEndOfFormer < rangeEndOfLatter) { + return -1; + } + + // b1 < b2 < a1 < a2 + if (rangeStartOfFormer > rangeStartOfLatter && rangeEndOfFormer > rangeEndOfLatter) { + return 1; + } + + // a1 < b1 < b2 < a2 + if (rangeStartOfFormer < rangeStartOfLatter && rangeEndOfFormer > rangeEndOfLatter) { + return -1; + } + + // b1 < a1 < a2 < b2 + if (rangeStartOfFormer > rangeStartOfLatter && rangeEndOfFormer < rangeEndOfLatter) { + return 1; + } + + return 0; + }); + + sortedStructuredClassNameNodes.forEach((classNameNode, index) => { + if (index === 0) { + return; + } + + const [rangeStartOfClassName, rangeEndOfClassName] = classNameNode.range; + const parentNodeCandidateIndex = sortedStructuredClassNameNodes + .slice(0, index) + .findLastIndex((parentCandidate) => { + const [rangeStartOfParentCandidate, rangeEndOfParentCandidate] = parentCandidate.range; + + return ( + rangeStartOfParentCandidate < rangeStartOfClassName && + rangeEndOfClassName < rangeEndOfParentCandidate + ); + }); + + if (parentNodeCandidateIndex !== -1) { + const parentNode = sortedStructuredClassNameNodes[parentNodeCandidateIndex]; + + // eslint-disable-next-line no-param-reassign + classNameNode.parent = parentNode; + parentNode.children.push(classNameNode); + } + }); + + return sortedStructuredClassNameNodes.filter((classNameNode) => !classNameNode.parent); +} + +function linearParse( + formattedText: string, + indentUnit: string, + structuredClassNameNodes: StructuredClassNameNode[], +): TextToken[] { + const sortedStructuredClassNameNodes = structuredClassNameNodes.slice().sort((former, latter) => { + const [rangeStartOfFormer] = former.range; + const [rangeStartOfLatter] = latter.range; + + return rangeStartOfLatter - rangeStartOfFormer; + }); + const textTokens: TextToken[] = []; + let temporaryRangeEnd = formattedText.length; + let rangeStartOfParent = 0; + let rangeEndOfParent = formattedText.length; + + for (let nodeIndex = 0; nodeIndex < sortedStructuredClassNameNodes.length; nodeIndex += 1) { + const structuredClassNameNode = sortedStructuredClassNameNodes[nodeIndex]; + const { type, range, parent, children, ...rest } = structuredClassNameNode; + const [rangeStartOfClassName, rangeEndOfClassName] = range; + + const delimiterOffset = + type === 'expression' && + structuredClassNameNode.isItAnObjectProperty && + structuredClassNameNode.delimiterType === 'backtick' + ? 1 + : 0; + + if (parent) { + [rangeStartOfParent, rangeEndOfParent] = parent.range; + + textTokens.push({ + type: 'Placeholder', + range: [rangeEndOfParent - 1, temporaryRangeEnd], + body: formattedText.slice(rangeEndOfParent - 1, temporaryRangeEnd), + }); + temporaryRangeEnd = rangeEndOfParent - 1; + } + + textTokens.push({ + type: 'Text', + range: [rangeEndOfClassName + delimiterOffset, temporaryRangeEnd], + body: formattedText.slice(rangeEndOfClassName + delimiterOffset, temporaryRangeEnd), + }); + textTokens.push({ + type: 'ClosingDelimiter', + range: [rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset], + body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset), + }); + + // ---------------------------------------------------------------- + + const classNameWithoutDelimiter = formattedText.slice( + rangeStartOfClassName + 1, + rangeEndOfClassName - 1, + ); + const classNameToken: TextToken = { + type, + range: [rangeStartOfClassName + 1, rangeEndOfClassName - 1], + body: classNameWithoutDelimiter, + }; + + if (children.length) { + const textTokensOfChildren = linearParse(formattedText, indentUnit, children); + + classNameToken.children = textTokensOfChildren; + classNameToken.body = textTokensOfChildren + .map((token) => (token.type === 'expression' ? token.frozenClassName : token.body)) + .join(''); + } + + classNameToken.props = rest; + + if (parent) { + const frozenClassName = freezeClassName(classNameWithoutDelimiter); + + classNameToken.frozenClassName = frozenClassName; + } + + textTokens.push(classNameToken); + + // ---------------------------------------------------------------- + + textTokens.push({ + type: 'OpeningDelimiter', + range: [rangeStartOfClassName - delimiterOffset, rangeStartOfClassName + 1], + body: formattedText.slice(rangeStartOfClassName - delimiterOffset, rangeStartOfClassName + 1), + }); + + temporaryRangeEnd = rangeStartOfClassName - delimiterOffset; + } + + if (rangeStartOfParent) { + textTokens.push({ + type: 'Text', + range: [rangeStartOfParent + 1, temporaryRangeEnd], + body: formattedText.slice(rangeStartOfParent + 1, temporaryRangeEnd), + }); + textTokens.push({ + type: 'Placeholder', + range: [0, rangeStartOfParent + 1], + body: formattedText.slice(0, rangeStartOfParent + 1), + }); + } else { + textTokens.push({ + type: 'Text', + range: [0, temporaryRangeEnd], + body: formattedText.slice(0, temporaryRangeEnd), + }); + } + + textTokens.reverse(); + + return textTokens.filter((token) => token.type !== 'Placeholder'); +} + +export function parseAndAssemble( + formattedText: string, + indentUnit: string, + targetClassNameNodes: ClassNameNode[], + options: ResolvedOptions, +): string { + if (options.debugFlag) { + console.dir(formattedText, inspectOptions); + console.dir(targetClassNameNodes, inspectOptions); + } + + const structuredClassNameNodes = structuringClassNameNodes( + targetClassNameNodes.filter(({ type }) => type !== 'ternary') as NonTernaryNode[], + ); + + if (options.debugFlag) { + console.dir(structuredClassNameNodes, inspectOptions); + } + + const textTokens = linearParse(formattedText, indentUnit, structuredClassNameNodes); + + if (options.debugFlag) { + console.dir(textTokens, inspectOptions); + } + + return formattedText; +} diff --git a/src/packages/core-parts/index.ts b/src/packages/core-parts/index.ts index 624e071..5bbdebf 100644 --- a/src/packages/core-parts/index.ts +++ b/src/packages/core-parts/index.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto'; -import { parseAndAssembleLikePpbs } from './experimental'; +import { parseAndAssemble } from './experimental'; import { findTargetClassNameNodes, findTargetClassNameNodesForHtml, @@ -465,7 +465,7 @@ export function parseLineByLineAndReplace({ } if (options.experimentalOptimization && options.parser === 'babel') { - return parseAndAssembleLikePpbs(formattedText, indentUnit, targetClassNameNodes, options); + return parseAndAssemble(formattedText, indentUnit, targetClassNameNodes, options); } const lineNodes = parseLineByLine(formattedText, indentUnit); @@ -801,7 +801,7 @@ export async function parseLineByLineAndReplaceAsync({ } if (options.experimentalOptimization && options.parser === 'babel') { - return parseAndAssembleLikePpbs(formattedText, indentUnit, targetClassNameNodes, options); + return parseAndAssemble(formattedText, indentUnit, targetClassNameNodes, options); } const lineNodes = parseLineByLine(formattedText, indentUnit); diff --git a/src/packages/core-parts/shared.ts b/src/packages/core-parts/shared.ts index 37ae993..880b98d 100644 --- a/src/packages/core-parts/shared.ts +++ b/src/packages/core-parts/shared.ts @@ -88,7 +88,7 @@ export type ExpressionNode = ClassNameNodeBase & { /** * In fact, the ternary operator itself is not a class name node, but it defines a type as an exception because it needs to be frozen when processing complex expressions. */ -type TernaryExpressionNode = ClassNameNodeBase & { +export type TernaryExpressionNode = ClassNameNodeBase & { type: 'ternary'; }; From dfb06c616a49fbdb3c713203ca2929290d865fc0 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Wed, 30 Oct 2024 00:24:10 +0900 Subject: [PATCH 08/20] feat: update linear parsing logic --- src/packages/core-parts/experimental.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index ed7ceb9..3670230 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -599,12 +599,14 @@ function linearParse( if (parent) { [rangeStartOfParent, rangeEndOfParent] = parent.range; - textTokens.push({ - type: 'Placeholder', - range: [rangeEndOfParent - 1, temporaryRangeEnd], - body: formattedText.slice(rangeEndOfParent - 1, temporaryRangeEnd), - }); - temporaryRangeEnd = rangeEndOfParent - 1; + if (rangeEndOfParent < temporaryRangeEnd) { + textTokens.push({ + type: 'Placeholder', + range: [rangeEndOfParent - 1, temporaryRangeEnd], + body: formattedText.slice(rangeEndOfParent - 1, temporaryRangeEnd), + }); + temporaryRangeEnd = rangeEndOfParent - 1; + } } textTokens.push({ From 4118fdb6ffd8f463e045ebf92309113efa524ac3 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Thu, 31 Oct 2024 23:45:06 +0900 Subject: [PATCH 09/20] feat: update linear parsing logic --- src/packages/core-parts/experimental.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 3670230..8e34f67 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -501,7 +501,7 @@ type TextToken = { body: string; frozenClassName?: string; children?: TextToken[]; - props?: Omit; + props?: Omit & { indentLevel: number }; }; function structuringClassNameNodes( @@ -573,6 +573,7 @@ function linearParse( indentUnit: string, structuredClassNameNodes: StructuredClassNameNode[], ): TextToken[] { + const formattedLines = formattedText.split(EOL); const sortedStructuredClassNameNodes = structuredClassNameNodes.slice().sort((former, latter) => { const [rangeStartOfFormer] = former.range; const [rangeStartOfLatter] = latter.range; @@ -586,9 +587,12 @@ function linearParse( for (let nodeIndex = 0; nodeIndex < sortedStructuredClassNameNodes.length; nodeIndex += 1) { const structuredClassNameNode = sortedStructuredClassNameNodes[nodeIndex]; - const { type, range, parent, children, ...rest } = structuredClassNameNode; + const { type, range, parent, children, startLineIndex, ...rest } = structuredClassNameNode; const [rangeStartOfClassName, rangeEndOfClassName] = range; + const indentMatchResult = formattedLines[startLineIndex].match(new RegExp(`^(${indentUnit})*`)); + const indentLevel = indentMatchResult![0].length / indentUnit.length; + const delimiterOffset = type === 'expression' && structuredClassNameNode.isItAnObjectProperty && @@ -641,7 +645,11 @@ function linearParse( .join(''); } - classNameToken.props = rest; + classNameToken.props = { + ...rest, + startLineIndex, + indentLevel, + }; if (parent) { const frozenClassName = freezeClassName(classNameWithoutDelimiter); From 2440ca1fd1b185130d02d6893d75633bbcf0dfe8 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 11:17:52 +0900 Subject: [PATCH 10/20] feat: implement attribute formatting --- src/packages/core-parts/experimental.ts | 66 +++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 8e34f67..7254bdb 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -504,6 +504,12 @@ type TextToken = { props?: Omit & { indentLevel: number }; }; +function assembleTokens(textTokens: TextToken[]): string { + return textTokens + .map((token) => (token.type === 'expression' ? token.frozenClassName : token.body)) + .join(''); +} + function structuringClassNameNodes( targetClassNameNodes: NonTernaryNode[], ): StructuredClassNameNode[] { @@ -640,9 +646,7 @@ function linearParse( const textTokensOfChildren = linearParse(formattedText, indentUnit, children); classNameToken.children = textTokensOfChildren; - classNameToken.body = textTokensOfChildren - .map((token) => (token.type === 'expression' ? token.frozenClassName : token.body)) - .join(''); + classNameToken.body = assembleTokens(textTokensOfChildren); } classNameToken.props = { @@ -694,6 +698,54 @@ function linearParse( return textTokens.filter((token) => token.type !== 'Placeholder'); } +function formatTokens( + textTokens: TextToken[], + indentUnit: string, + options: ResolvedOptions, +): TextToken[] { + const formattedTokens = structuredClone(textTokens); + const isStartingPositionRelative = options.endingPosition !== 'absolute'; + const isEndingPositionAbsolute = options.endingPosition !== 'relative'; + const isOutputIdeal = isStartingPositionRelative && isEndingPositionAbsolute; + + for (let tokenIndex = formattedTokens.length - 1; tokenIndex >= 0; tokenIndex -= 1) { + const token = formattedTokens[tokenIndex]; + + if (token.type === 'attribute') { + const props = token.props!; + const leadingText = isEndingPositionAbsolute + ? assembleTokens(formattedTokens.slice(0, tokenIndex)).split(EOL).slice(-1).join('') + : ''; + const classNameBase = `${PH.repeat(leadingText.length)}${token.body.trim()}`; + + let formattedClassName = formatClassName(classNameBase, options.printWidth).slice( + leadingText.length, + ); + const formattedLines = formattedClassName.split(EOL); + const isMultiLineClassName = formattedLines.length > 1; + + if (isMultiLineClassName) { + const multiLineIndentLevel = isStartingPositionRelative ? props.indentLevel + 1 : 0; + + formattedClassName = [ + formattedLines[0], + ...(isOutputIdeal + ? formatClassName( + formattedLines.slice(1).join(EOL), + options.printWidth - options.tabWidth * multiLineIndentLevel, + ).split(EOL) + : formattedLines.slice(1)), + ].join(`${EOL}${indentUnit.repeat(multiLineIndentLevel)}`); + } + + token.type = 'Text'; + token.body = formattedClassName; + } + } + + return formattedTokens; +} + export function parseAndAssemble( formattedText: string, indentUnit: string, @@ -719,5 +771,11 @@ export function parseAndAssemble( console.dir(textTokens, inspectOptions); } - return formattedText; + const formattedTokens = formatTokens(textTokens, indentUnit, options); + + if (options.debugFlag) { + console.dir(formattedTokens, inspectOptions); + } + + return assembleTokens(formattedTokens); } From 4c2baa61154dd9729f50f458f1e28520f4bbec3a Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 20:24:39 +0900 Subject: [PATCH 11/20] feat: implement expression formatting --- src/packages/core-parts/experimental.ts | 135 +++++++++++++++++++++++- 1 file changed, 132 insertions(+), 3 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 7254bdb..6a75264 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -506,7 +506,9 @@ type TextToken = { function assembleTokens(textTokens: TextToken[]): string { return textTokens - .map((token) => (token.type === 'expression' ? token.frozenClassName : token.body)) + .map((token) => + token.type === 'expression' ? token.frozenClassName ?? token.body : token.body, + ) .join(''); } @@ -738,7 +740,103 @@ function formatTokens( ].join(`${EOL}${indentUnit.repeat(multiLineIndentLevel)}`); } - token.type = 'Text'; + token.body = formattedClassName; + } else if (token.type === 'expression') { + const props = token.props as Omit & { + indentLevel: number; + }; + const leadingText = isEndingPositionAbsolute + ? assembleTokens(formattedTokens.slice(0, tokenIndex)).split(EOL).slice(-1).join('') + : ''; + const hasLeadingSpace = token.body !== token.body.trimStart(); + const hasTrailingSpace = token.body !== token.body.trimEnd(); + const classNameBase = `${PH.repeat(leadingText.length)}${ + hasLeadingSpace ? SPACE : '' + }${token.body.trim().replace(/\\\n/g, '')}`; + + let formattedClassName = `${formatClassName(classNameBase, options.printWidth).slice( + leadingText.length, + )}${hasTrailingSpace ? SPACE : ''}`; + const formattedLines = formattedClassName.split(EOL); + const isMultiLineClassName = formattedLines.length > 1; + + if (isMultiLineClassName) { + // eslint-disable-next-line no-nested-ternary + const multiLineIndentLevel = isStartingPositionRelative + ? props.isTheFirstLineOnTheSameLineAsTheAttributeName || + props.isItAnOperandOfTernaryOperator + ? props.indentLevel + 1 + : props.indentLevel + : 0; + + formattedClassName = [ + formattedLines[0], + ...(isOutputIdeal + ? `${formatClassName( + formattedLines.slice(1).join(EOL), + options.printWidth - options.tabWidth * multiLineIndentLevel, + )}${hasTrailingSpace ? SPACE : ''}`.split(EOL) + : formattedLines.slice(1)), + ].join(`${EOL}${indentUnit.repeat(multiLineIndentLevel)}`); + + const areNeededBrackets = props.isItAnObjectProperty; + + formattedTokens[tokenIndex - 1].body = `${areNeededBrackets ? '[' : ''}${BACKTICK}`; + formattedTokens[tokenIndex + 1].body = `${BACKTICK}${areNeededBrackets ? ']' : ''}`; + + if (props.delimiterType !== 'backtick' && props.hasBacktick) { + formattedClassName = formattedClassName.replace(/`/g, `\\${BACKTICK}`); + } + } else { + let baseDelimiter = DOUBLE_QUOTE; + + if (props.shouldKeepDelimiter) { + if (props.delimiterType === 'backtick') { + baseDelimiter = BACKTICK; + } else if (props.delimiterType === 'single-quote') { + baseDelimiter = SINGLE_QUOTE; + } else { + // baseDelimiter = DOUBLE_QUOTE; + } + } else if (props.isItInVueTemplate) { + baseDelimiter = SINGLE_QUOTE; + } else if (options.singleQuote) { + if (props.hasSingleQuote) { + // baseDelimiter = DOUBLE_QUOTE; + } else { + baseDelimiter = SINGLE_QUOTE; + } + } else if (!options.singleQuote) { + if (props.hasDoubleQuote) { + baseDelimiter = SINGLE_QUOTE; + } else { + // baseDelimiter = DOUBLE_QUOTE; + } + } + + formattedTokens[tokenIndex - 1].body = baseDelimiter; + formattedTokens[tokenIndex + 1].body = baseDelimiter; + + if (baseDelimiter === SINGLE_QUOTE) { + if (props.delimiterType !== 'single-quote' && props.hasSingleQuote) { + formattedClassName = formattedClassName.replace(/'/g, `\\${SINGLE_QUOTE}`); + } + } else if (baseDelimiter === DOUBLE_QUOTE) { + if (props.delimiterType !== 'double-quote' && props.hasDoubleQuote) { + formattedClassName = formattedClassName.replace(/"/g, `\\${DOUBLE_QUOTE}`); + } + } else { + // eslint-disable-next-line no-lonely-if + if (props.delimiterType !== 'backtick' && props.hasBacktick) { + formattedClassName = formattedClassName.replace(/`/g, `\\${BACKTICK}`); + } + } + } + + if (token.children?.length) { + token.children = formatTokens(token.children, indentUnit, options); + } + token.body = formattedClassName; } } @@ -746,6 +844,37 @@ function formatTokens( return formattedTokens; } +function unfreezeToken(token: TextToken): string { + if (token.children?.length) { + for (let index = token.children.length - 1; index >= 0; index -= 1) { + const tokenOfChildren = token.children[index]; + + if (tokenOfChildren.type === 'expression' && tokenOfChildren.frozenClassName) { + const props = tokenOfChildren.props as Omit & { + indentLevel: number; + }; + const originalDelimiter = + // eslint-disable-next-line no-nested-ternary + props.delimiterType === 'single-quote' + ? SINGLE_QUOTE + : props.delimiterType === 'double-quote' + ? DOUBLE_QUOTE + : BACKTICK; + + // eslint-disable-next-line no-param-reassign + token.body = token.body.replace( + `${originalDelimiter}${tokenOfChildren.frozenClassName}${originalDelimiter}`, + `${token.children[index - 1].body}${unfreezeToken(tokenOfChildren)}${ + token.children[index + 1].body + }`, + ); + } + } + } + + return token.body; +} + export function parseAndAssemble( formattedText: string, indentUnit: string, @@ -777,5 +906,5 @@ export function parseAndAssemble( console.dir(formattedTokens, inspectOptions); } - return assembleTokens(formattedTokens); + return formattedTokens.map(unfreezeToken).join(''); } From 89c757b5dc0e660d85098d974ceca55f285bbcfe Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 21:01:52 +0900 Subject: [PATCH 12/20] feat: fix indent handling logic --- src/packages/core-parts/experimental.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 6a75264..176f7ca 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -716,7 +716,11 @@ function formatTokens( if (token.type === 'attribute') { const props = token.props!; const leadingText = isEndingPositionAbsolute - ? assembleTokens(formattedTokens.slice(0, tokenIndex)).split(EOL).slice(-1).join('') + ? assembleTokens(formattedTokens.slice(0, tokenIndex)) + .split(EOL) + .slice(-1) + .join('') + .replace(/\t/g, SPACE.repeat(options.tabWidth)) : ''; const classNameBase = `${PH.repeat(leadingText.length)}${token.body.trim()}`; @@ -746,7 +750,11 @@ function formatTokens( indentLevel: number; }; const leadingText = isEndingPositionAbsolute - ? assembleTokens(formattedTokens.slice(0, tokenIndex)).split(EOL).slice(-1).join('') + ? assembleTokens(formattedTokens.slice(0, tokenIndex)) + .split(EOL) + .slice(-1) + .join('') + .replace(/\t/g, SPACE.repeat(options.tabWidth)) : ''; const hasLeadingSpace = token.body !== token.body.trimStart(); const hasTrailingSpace = token.body !== token.body.trimEnd(); From 62e92f5effab263fe164377496258b5bf66bebc1 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 21:41:58 +0900 Subject: [PATCH 13/20] feat: fix empty string handling logic --- src/packages/core-parts/experimental.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 176f7ca..191bd1c 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -857,7 +857,7 @@ function unfreezeToken(token: TextToken): string { for (let index = token.children.length - 1; index >= 0; index -= 1) { const tokenOfChildren = token.children[index]; - if (tokenOfChildren.type === 'expression' && tokenOfChildren.frozenClassName) { + if (tokenOfChildren.type === 'expression' && tokenOfChildren.frozenClassName !== undefined) { const props = tokenOfChildren.props as Omit & { indentLevel: number; }; From 5f915976bc3458a5b8522e1c5c1af7cce7a86386 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 21:54:42 +0900 Subject: [PATCH 14/20] refactor: tidy up unnecessary code --- src/packages/core-parts/experimental.ts | 434 +----------------------- 1 file changed, 1 insertion(+), 433 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 191bd1c..1221f7b 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,25 +1,8 @@ import { createHash } from 'node:crypto'; -import type { - Dict, - NodeRange, - ExpressionNode, - TernaryExpressionNode, - ClassNameNode, -} from './shared'; +import type { NodeRange, ExpressionNode, TernaryExpressionNode, ClassNameNode } from './shared'; import { EOL, PH, SPACE, SINGLE_QUOTE, DOUBLE_QUOTE, BACKTICK } from './shared'; -type LinePart = { - type: string; - body: string; - props?: Omit; -}; - -type LineNode = { - indentLevel: number; - parts: LinePart[]; -}; - const inspectOptions: any = { depth: null }; function sha1(input: string): string { @@ -43,131 +26,6 @@ function freezeClassName(input: string): string { return `${prefix}${rest}`; } -function parseLineByLineFromBottomToTop( - formattedText: string, - indentUnit: string, - targetClassNameNodes: ClassNameNode[], -): LineNode[] { - const formattedLines = formattedText.split(EOL); - let rangeStartOfLine: number; - let rangeEndOfLine = formattedText.length; - const rangePerLine: Dict = {}; - - const parsedLineNodes: LineNode[] = []; - - for ( - let currentLineIndex = formattedLines.length - 1; - currentLineIndex >= 0; - currentLineIndex -= 1 - ) { - const line = formattedLines[currentLineIndex]; - - const indentMatchResult = line.match(new RegExp(`^(${indentUnit})*`)); - const indentLevel = indentMatchResult![0].length / indentUnit.length; - const indentOffset = indentUnit.length * indentLevel; - const parts: LinePart[] = []; - - rangeStartOfLine = rangeEndOfLine - line.length; - rangePerLine[currentLineIndex] = [rangeStartOfLine, rangeEndOfLine]; - - const classNameNodesStartingFromCurrentLine = targetClassNameNodes.filter( - ({ startLineIndex }) => startLineIndex === currentLineIndex, - ); - - let rangeEndOfPart = rangeEndOfLine; - - for ( - let nodeIndex = 0; - nodeIndex < classNameNodesStartingFromCurrentLine.length; - nodeIndex += 1 - ) { - const classNameNode = classNameNodesStartingFromCurrentLine[nodeIndex]; - const { - range, - startLineIndex, // not in use - ...restWithType - } = classNameNode; - const { type, ...restWithoutType } = restWithType; - const [rangeStartOfClassName, rangeEndOfClassName] = range; - - const classNameWithoutDelimiter = formattedText.slice( - rangeStartOfClassName + 1, - rangeEndOfClassName - 1, - ); - const numberOfLinesOfClassName = classNameWithoutDelimiter.split(EOL).length; - const numberOfLinesToSkip = numberOfLinesOfClassName - 1; - const rangeOfLineWhereClassNameEnds = rangePerLine[currentLineIndex + numberOfLinesToSkip]; - - if (rangeOfLineWhereClassNameEnds) { - const [, rangeEndOfLineWhereClassNameEnds] = rangeOfLineWhereClassNameEnds; - - const delimiterOffset = - restWithType.type === 'expression' && - restWithType.isItAnObjectProperty && - restWithType.delimiterType === 'backtick' - ? 1 - : 0; - - parts.push({ - type: 'Text', - body: formattedText.slice( - rangeEndOfClassName + delimiterOffset, - rangeEndOfClassName + delimiterOffset < rangeEndOfLineWhereClassNameEnds - ? rangeEndOfLineWhereClassNameEnds - : rangeEndOfPart, - ), - }); - parts.push({ - type: 'ClosingDelimiter', - body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset), - }); - parts.push({ - type, - body: classNameWithoutDelimiter, - props: restWithoutType, - }); - parts.push({ - type: 'OpeningDelimiter', - body: formattedText.slice( - rangeStartOfClassName - delimiterOffset, - rangeStartOfClassName + 1, - ), - }); - - rangeEndOfPart = rangeStartOfClassName - delimiterOffset; - } - - if (numberOfLinesToSkip) { - parsedLineNodes.splice(parsedLineNodes.length - numberOfLinesToSkip, numberOfLinesToSkip); - } - } - - parts.push({ - type: 'Text', - body: formattedText.slice(rangeStartOfLine, rangeEndOfPart).slice(indentOffset), - }); - parts.reverse(); - - if (parts.length > 1 && parts[0].body === '') { - parts.shift(); - } - if (parts.length > 1 && parts[parts.length - 1].body === '') { - parts.pop(); - } - - parsedLineNodes.push({ - indentLevel, - parts, - }); - - rangeEndOfLine = rangeStartOfLine - EOL.length; - } - - parsedLineNodes.reverse(); - - return parsedLineNodes; -} - function formatClassName(source: string, printWidth: number): string { const lines: string[] = []; let remainder = `${source}${SPACE}`.replace(/[\s]+/g, SPACE); @@ -198,296 +56,6 @@ function formatClassName(source: string, printWidth: number): string { return lines.join(EOL); } -function transformClassNameIfNecessary(lineNodes: LineNode[], options: ResolvedOptions): void { - const isStartingPositionRelative = options.endingPosition !== 'absolute'; - const isEndingPositionAbsolute = options.endingPosition !== 'relative'; - const isOutputIdeal = isStartingPositionRelative && isEndingPositionAbsolute; - - for (let lineIndex = lineNodes.length - 1; lineIndex >= 0; lineIndex -= 1) { - const { indentLevel, parts } = lineNodes[lineIndex]; - const temporaryLineNodes: LineNode[] = []; - - let temporaryPartIndex = parts.length; - - for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) { - const currentPart = parts[partIndex]; - - if (currentPart.type === 'attribute') { - const slicedLineNodes: LineNode[] = []; - const leadingText = isEndingPositionAbsolute - ? `${SPACE.repeat(options.tabWidth * indentLevel)}${parts - .slice(0, partIndex) - .map(({ body }) => body) - .join('')}` - : ''; - const classNameBase = `${PH.repeat(leadingText.length)}${currentPart.body.trim()}`; - - let formattedClassName = formatClassName(classNameBase, options.printWidth).slice( - leadingText.length, - ); - if (options.debugFlag) { - // eslint-disable-next-line no-console - console.dir(formattedClassName, inspectOptions); - } - const lines = formattedClassName.split(EOL); - const isMultiLineClassName = lines.length > 1; - - if (isMultiLineClassName) { - if (isOutputIdeal) { - const multiLineIndentLevel = indentLevel + 1; - - formattedClassName = [ - lines[0], - formatClassName( - lines.slice(1).join(EOL), - options.printWidth - options.tabWidth * multiLineIndentLevel, - ), - ].join(EOL); - } - - slicedLineNodes.push( - ...formattedClassName.split(EOL).map((line) => ({ - indentLevel, - parts: [ - { - type: 'Text', - body: line, - }, - ], - })), - ); - - slicedLineNodes[slicedLineNodes.length - 1].parts.push( - ...parts.splice(partIndex + 1, parts.length - (partIndex + 1)), - ); - - parts.splice(partIndex, 1, ...slicedLineNodes.splice(0, 1)[0].parts); - - slicedLineNodes.forEach((lineNode) => { - if (isStartingPositionRelative) { - // eslint-disable-next-line no-param-reassign - lineNode.indentLevel += 1; - } else { - // eslint-disable-next-line no-param-reassign - lineNode.indentLevel = 0; - } - }); - - slicedLineNodes.reverse(); - - temporaryLineNodes.push(...slicedLineNodes); - temporaryPartIndex = partIndex + 1; - } else { - currentPart.type = 'Text'; - currentPart.body = formattedClassName; - } - } else if (currentPart.type === 'expression') { - const props = currentPart.props as Omit< - ExpressionNode, - 'type' | 'range' | 'startLineIndex' - >; - - const slicedLineNodes: LineNode[] = []; - const leadingText = isEndingPositionAbsolute - ? `${SPACE.repeat(options.tabWidth * indentLevel)}${parts - .slice(0, partIndex) - .map(({ body }) => body) - .join('')}` - : ''; - const hasLeadingSpace = currentPart.body !== currentPart.body.trimStart(); - const hasTrailingSpace = currentPart.body !== currentPart.body.trimEnd(); - const classNameBase = `${PH.repeat(leadingText.length)}${ - hasLeadingSpace ? SPACE : '' - }${currentPart.body.trim().replace(/\\\n/g, '')}`; - - let formattedClassName = `${formatClassName(classNameBase, options.printWidth).slice( - leadingText.length, - )}${hasTrailingSpace ? SPACE : ''}`; - if (options.debugFlag) { - // eslint-disable-next-line no-console - console.dir(formattedClassName, inspectOptions); - } - const lines = formattedClassName.split(EOL); - const isMultiLineClassName = lines.length > 1; - - if (isMultiLineClassName) { - if (isOutputIdeal) { - const multiLineIndentLevel = - props.isTheFirstLineOnTheSameLineAsTheAttributeName || - props.isItAnOperandOfTernaryOperator - ? indentLevel + 1 - : indentLevel; - - formattedClassName = [ - lines[0], - `${formatClassName( - lines.slice(1).join(EOL), - options.printWidth - options.tabWidth * multiLineIndentLevel, - )}${hasTrailingSpace ? SPACE : ''}`, - ].join(EOL); - } - - if (props.delimiterType !== 'backtick' && props.hasBacktick) { - formattedClassName = formattedClassName.replace(/`/g, `\\${BACKTICK}`); - } - - slicedLineNodes.push( - ...formattedClassName.split(EOL).map((line) => ({ - indentLevel, - parts: [ - { - type: 'Text', - body: line, - }, - ], - })), - ); - - const areNeededBrackets = props.isItAnObjectProperty; - - parts[partIndex - 1].body = `${areNeededBrackets ? '[' : ''}${BACKTICK}`; - parts[partIndex + 1].body = `${BACKTICK}${areNeededBrackets ? ']' : ''}`; - - slicedLineNodes[slicedLineNodes.length - 1].parts.push( - ...parts.splice(partIndex + 1, parts.length - (partIndex + 1)), - ); - - parts.splice(partIndex, 1, ...slicedLineNodes.splice(0, 1)[0].parts); - - slicedLineNodes.forEach((lineNode) => { - if (isStartingPositionRelative) { - if ( - props.isTheFirstLineOnTheSameLineAsTheAttributeName || - props.isItAnOperandOfTernaryOperator - ) { - // eslint-disable-next-line no-param-reassign - lineNode.indentLevel += 1; - } - } else { - // eslint-disable-next-line no-param-reassign - lineNode.indentLevel = 0; - } - }); - - slicedLineNodes.reverse(); - - temporaryLineNodes.push(...slicedLineNodes); - temporaryPartIndex = partIndex + 1; - } else { - currentPart.type = 'Text'; - currentPart.body = formattedClassName; - - let baseDelimiter = DOUBLE_QUOTE; - - if (props.shouldKeepDelimiter) { - if (props.delimiterType === 'backtick') { - baseDelimiter = BACKTICK; - } else if (props.delimiterType === 'single-quote') { - baseDelimiter = SINGLE_QUOTE; - } else { - // baseDelimiter = DOUBLE_QUOTE; - } - } else if (props.isItInVueTemplate) { - baseDelimiter = SINGLE_QUOTE; - } else if (options.singleQuote) { - if (props.hasSingleQuote) { - // baseDelimiter = DOUBLE_QUOTE; - } else { - baseDelimiter = SINGLE_QUOTE; - } - } else if (!options.singleQuote) { - if (props.hasDoubleQuote) { - baseDelimiter = SINGLE_QUOTE; - } else { - // baseDelimiter = DOUBLE_QUOTE; - } - } - - parts[partIndex - 1].body = baseDelimiter; - parts[partIndex + 1].body = baseDelimiter; - - if (baseDelimiter === SINGLE_QUOTE) { - if (props.delimiterType !== 'single-quote' && props.hasSingleQuote) { - currentPart.body = formattedClassName.replace(/'/g, `\\${SINGLE_QUOTE}`); - } - } else if (baseDelimiter === DOUBLE_QUOTE) { - if (props.delimiterType !== 'double-quote' && props.hasDoubleQuote) { - currentPart.body = formattedClassName.replace(/"/g, `\\${DOUBLE_QUOTE}`); - } - } else { - // eslint-disable-next-line no-lonely-if - if (props.delimiterType !== 'backtick' && props.hasBacktick) { - currentPart.body = formattedClassName.replace(/`/g, `\\${BACKTICK}`); - } - } - } - } - } - - temporaryLineNodes.push({ - indentLevel, - parts: parts.slice(0, temporaryPartIndex), - }); - temporaryLineNodes.reverse(); - - lineNodes.splice( - lineIndex, - 1, - ...temporaryLineNodes.filter((lineNode) => lineNode.parts.length), - ); - } -} - -function assembleLine(lineNodes: LineNode[], indentUnit: string): string { - return lineNodes - .map( - ({ indentLevel, parts }) => - `${indentUnit.repeat(indentLevel)}${parts.map(({ body }) => body).join('')}`, - ) - .join(EOL); -} - -/** - * Parse and assemble like `prettier-plugin-brace-style` - * - * @deprecated - */ -export function parseAndAssembleLikePpbs( - formattedText: string, - indentUnit: string, - targetClassNameNodes: ClassNameNode[], - options: ResolvedOptions, -): string { - if (options.debugFlag) { - // eslint-disable-next-line no-console - console.dir(formattedText, inspectOptions); - // eslint-disable-next-line no-console - console.dir(targetClassNameNodes, inspectOptions); - } - - const lineNodes = parseLineByLineFromBottomToTop( - formattedText, - indentUnit, - targetClassNameNodes.filter(({ type }) => type !== 'ternary'), - ); - - if (options.debugFlag) { - // eslint-disable-next-line no-console - console.dir(lineNodes, inspectOptions); - } - - transformClassNameIfNecessary(lineNodes, options); - - if (options.debugFlag) { - // eslint-disable-next-line no-console - console.dir(lineNodes, inspectOptions); - } - - return assembleLine(lineNodes, indentUnit); -} - -// ---------------------------------------------------------------- - type NonTernaryNode = Exclude; type StructuredClassNameNode = NonTernaryNode & { From 4494bb5a4242ff613f5f689e1172095a0c600d3e Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 22:01:04 +0900 Subject: [PATCH 15/20] refactor: remove debugging logs --- src/packages/core-parts/experimental.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 1221f7b..cb13954 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -3,8 +3,6 @@ import { createHash } from 'node:crypto'; import type { NodeRange, ExpressionNode, TernaryExpressionNode, ClassNameNode } from './shared'; import { EOL, PH, SPACE, SINGLE_QUOTE, DOUBLE_QUOTE, BACKTICK } from './shared'; -const inspectOptions: any = { depth: null }; - function sha1(input: string): string { return createHash('sha1').update(input).digest('hex'); } @@ -457,30 +455,13 @@ export function parseAndAssemble( targetClassNameNodes: ClassNameNode[], options: ResolvedOptions, ): string { - if (options.debugFlag) { - console.dir(formattedText, inspectOptions); - console.dir(targetClassNameNodes, inspectOptions); - } - const structuredClassNameNodes = structuringClassNameNodes( targetClassNameNodes.filter(({ type }) => type !== 'ternary') as NonTernaryNode[], ); - if (options.debugFlag) { - console.dir(structuredClassNameNodes, inspectOptions); - } - const textTokens = linearParse(formattedText, indentUnit, structuredClassNameNodes); - if (options.debugFlag) { - console.dir(textTokens, inspectOptions); - } - const formattedTokens = formatTokens(textTokens, indentUnit, options); - if (options.debugFlag) { - console.dir(formattedTokens, inspectOptions); - } - return formattedTokens.map(unfreezeToken).join(''); } From 73d8ff22e41a235e6fbe62c0471ff7a1d5f917ea Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 23:13:43 +0900 Subject: [PATCH 16/20] feat: fix angular formatting logic --- src/packages/core-parts/experimental.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index cb13954..1bfea7a 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -353,13 +353,18 @@ function formatTokens( : formattedLines.slice(1)), ].join(`${EOL}${indentUnit.repeat(multiLineIndentLevel)}`); - const areNeededBrackets = props.isItAnObjectProperty; + if (props.isItAngularExpression) { + formattedTokens[tokenIndex - 1].body = SINGLE_QUOTE; + formattedTokens[tokenIndex + 1].body = SINGLE_QUOTE; + } else { + const areNeededBrackets = props.isItAnObjectProperty; - formattedTokens[tokenIndex - 1].body = `${areNeededBrackets ? '[' : ''}${BACKTICK}`; - formattedTokens[tokenIndex + 1].body = `${BACKTICK}${areNeededBrackets ? ']' : ''}`; + formattedTokens[tokenIndex - 1].body = `${areNeededBrackets ? '[' : ''}${BACKTICK}`; + formattedTokens[tokenIndex + 1].body = `${BACKTICK}${areNeededBrackets ? ']' : ''}`; - if (props.delimiterType !== 'backtick' && props.hasBacktick) { - formattedClassName = formattedClassName.replace(/`/g, `\\${BACKTICK}`); + if (props.delimiterType !== 'backtick' && props.hasBacktick) { + formattedClassName = formattedClassName.replace(/`/g, `\\${BACKTICK}`); + } } } else { let baseDelimiter = DOUBLE_QUOTE; @@ -388,6 +393,10 @@ function formatTokens( } } + if (props.isItAngularExpression) { + baseDelimiter = SINGLE_QUOTE; + } + formattedTokens[tokenIndex - 1].body = baseDelimiter; formattedTokens[tokenIndex + 1].body = baseDelimiter; From ca25086de1c7caf3b01820ce7219c0f3c6087d8b Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Fri, 1 Nov 2024 23:47:57 +0900 Subject: [PATCH 17/20] feat: make experimental logic apply to all parsers --- src/packages/core-parts/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/core-parts/index.ts b/src/packages/core-parts/index.ts index 5bbdebf..885bde4 100644 --- a/src/packages/core-parts/index.ts +++ b/src/packages/core-parts/index.ts @@ -464,7 +464,7 @@ export function parseLineByLineAndReplace({ } } - if (options.experimentalOptimization && options.parser === 'babel') { + if (options.experimentalOptimization) { return parseAndAssemble(formattedText, indentUnit, targetClassNameNodes, options); } @@ -800,7 +800,7 @@ export async function parseLineByLineAndReplaceAsync({ } } - if (options.experimentalOptimization && options.parser === 'babel') { + if (options.experimentalOptimization) { return parseAndAssemble(formattedText, indentUnit, targetClassNameNodes, options); } From bdae0382a51e53cfae79c1488581dcfa8190ec13 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sat, 2 Nov 2024 09:14:35 +0900 Subject: [PATCH 18/20] feat: implement ternary freezing --- src/packages/core-parts/experimental.ts | 249 +++++++++++++++--------- src/packages/core-parts/shared.ts | 2 +- 2 files changed, 158 insertions(+), 93 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 1bfea7a..142294d 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto'; -import type { NodeRange, ExpressionNode, TernaryExpressionNode, ClassNameNode } from './shared'; +import type { NodeRange, ExpressionNode, ClassNameNode } from './shared'; import { EOL, PH, SPACE, SINGLE_QUOTE, DOUBLE_QUOTE, BACKTICK } from './shared'; function sha1(input: string): string { @@ -54,9 +54,7 @@ function formatClassName(source: string, printWidth: number): string { return lines.join(EOL); } -type NonTernaryNode = Exclude; - -type StructuredClassNameNode = NonTernaryNode & { +type StructuredClassNameNode = ClassNameNode & { parent?: StructuredClassNameNode; children: StructuredClassNameNode[]; }; @@ -67,19 +65,15 @@ type TextToken = { body: string; frozenClassName?: string; children?: TextToken[]; - props?: Omit & { indentLevel: number }; + props?: Omit & { indentLevel: number }; }; function assembleTokens(textTokens: TextToken[]): string { - return textTokens - .map((token) => - token.type === 'expression' ? token.frozenClassName ?? token.body : token.body, - ) - .join(''); + return textTokens.map((token) => token.frozenClassName ?? token.body).join(''); } function structuringClassNameNodes( - targetClassNameNodes: NonTernaryNode[], + targetClassNameNodes: ClassNameNode[], ): StructuredClassNameNode[] { const sortedStructuredClassNameNodes: StructuredClassNameNode[] = targetClassNameNodes .map((classNameNode) => ({ @@ -100,13 +94,13 @@ function structuringClassNameNodes( return 1; } - // a1 < b1 < b2 < a2 - if (rangeStartOfFormer < rangeStartOfLatter && rangeEndOfFormer > rangeEndOfLatter) { + // a1 < b1 < b2 <= a2 + if (rangeStartOfFormer < rangeStartOfLatter && rangeEndOfFormer >= rangeEndOfLatter) { return -1; } - // b1 < a1 < a2 < b2 - if (rangeStartOfFormer > rangeStartOfLatter && rangeEndOfFormer < rangeEndOfLatter) { + // b1 < a1 < a2 <= b2 + if (rangeStartOfFormer > rangeStartOfLatter && rangeEndOfFormer <= rangeEndOfLatter) { return 1; } @@ -126,7 +120,7 @@ function structuringClassNameNodes( return ( rangeStartOfParentCandidate < rangeStartOfClassName && - rangeEndOfClassName < rangeEndOfParentCandidate + rangeEndOfClassName <= rangeEndOfParentCandidate ); }); @@ -178,12 +172,21 @@ function linearParse( [rangeStartOfParent, rangeEndOfParent] = parent.range; if (rangeEndOfParent < temporaryRangeEnd) { - textTokens.push({ - type: 'Placeholder', - range: [rangeEndOfParent - 1, temporaryRangeEnd], - body: formattedText.slice(rangeEndOfParent - 1, temporaryRangeEnd), - }); - temporaryRangeEnd = rangeEndOfParent - 1; + if (parent.type === 'ternary') { + textTokens.push({ + type: 'Placeholder', + range: [rangeEndOfParent, temporaryRangeEnd], + body: formattedText.slice(rangeEndOfParent, temporaryRangeEnd), + }); + temporaryRangeEnd = rangeEndOfParent; + } else { + textTokens.push({ + type: 'Placeholder', + range: [rangeEndOfParent - 1, temporaryRangeEnd], + body: formattedText.slice(rangeEndOfParent - 1, temporaryRangeEnd), + }); + temporaryRangeEnd = rangeEndOfParent - 1; + } } } @@ -192,67 +195,119 @@ function linearParse( range: [rangeEndOfClassName + delimiterOffset, temporaryRangeEnd], body: formattedText.slice(rangeEndOfClassName + delimiterOffset, temporaryRangeEnd), }); - textTokens.push({ - type: 'ClosingDelimiter', - range: [rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset], - body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset), - }); - // ---------------------------------------------------------------- + if (type === 'ternary') { + const ternaryExpression = formattedText.slice(rangeStartOfClassName, rangeEndOfClassName); + const ternaryToken: TextToken = { + type, + range: [rangeStartOfClassName, rangeEndOfClassName], + body: ternaryExpression, + }; - const classNameWithoutDelimiter = formattedText.slice( - rangeStartOfClassName + 1, - rangeEndOfClassName - 1, - ); - const classNameToken: TextToken = { - type, - range: [rangeStartOfClassName + 1, rangeEndOfClassName - 1], - body: classNameWithoutDelimiter, - }; + if (children.length) { + const textTokensOfChildren = linearParse(formattedText, indentUnit, children); - if (children.length) { - const textTokensOfChildren = linearParse(formattedText, indentUnit, children); + ternaryToken.children = textTokensOfChildren; + ternaryToken.body = assembleTokens(textTokensOfChildren); + } - classNameToken.children = textTokensOfChildren; - classNameToken.body = assembleTokens(textTokensOfChildren); - } + ternaryToken.props = { + ...rest, + startLineIndex, + indentLevel, + }; - classNameToken.props = { - ...rest, - startLineIndex, - indentLevel, - }; + if (parent) { + const frozenClassName = freezeClassName(ternaryExpression); - if (parent) { - const frozenClassName = freezeClassName(classNameWithoutDelimiter); + ternaryToken.frozenClassName = frozenClassName; + } - classNameToken.frozenClassName = frozenClassName; - } + textTokens.push(ternaryToken); - textTokens.push(classNameToken); + temporaryRangeEnd = rangeStartOfClassName; + } else { + textTokens.push({ + type: 'ClosingDelimiter', + range: [rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset], + body: formattedText.slice(rangeEndOfClassName - 1, rangeEndOfClassName + delimiterOffset), + }); - // ---------------------------------------------------------------- + // ---------------------------------------------------------------- - textTokens.push({ - type: 'OpeningDelimiter', - range: [rangeStartOfClassName - delimiterOffset, rangeStartOfClassName + 1], - body: formattedText.slice(rangeStartOfClassName - delimiterOffset, rangeStartOfClassName + 1), - }); + const classNameWithoutDelimiter = formattedText.slice( + rangeStartOfClassName + 1, + rangeEndOfClassName - 1, + ); + const classNameToken: TextToken = { + type, + range: [rangeStartOfClassName + 1, rangeEndOfClassName - 1], + body: classNameWithoutDelimiter, + }; + + if (children.length) { + const textTokensOfChildren = linearParse(formattedText, indentUnit, children); + + classNameToken.children = textTokensOfChildren; + classNameToken.body = assembleTokens(textTokensOfChildren); + } + + classNameToken.props = { + ...rest, + startLineIndex, + indentLevel, + }; - temporaryRangeEnd = rangeStartOfClassName - delimiterOffset; + if (parent) { + const frozenClassName = freezeClassName(classNameWithoutDelimiter); + + classNameToken.frozenClassName = frozenClassName; + } + + textTokens.push(classNameToken); + + // ---------------------------------------------------------------- + + textTokens.push({ + type: 'OpeningDelimiter', + range: [rangeStartOfClassName - delimiterOffset, rangeStartOfClassName + 1], + body: formattedText.slice( + rangeStartOfClassName - delimiterOffset, + rangeStartOfClassName + 1, + ), + }); + + temporaryRangeEnd = rangeStartOfClassName - delimiterOffset; + } } + // Note: Since parent cannot be accessed directly outside of the for loop, I check for changes that may occur when parent exists. + // if (parent) if (rangeStartOfParent) { - textTokens.push({ - type: 'Text', - range: [rangeStartOfParent + 1, temporaryRangeEnd], - body: formattedText.slice(rangeStartOfParent + 1, temporaryRangeEnd), - }); - textTokens.push({ - type: 'Placeholder', - range: [0, rangeStartOfParent + 1], - body: formattedText.slice(0, rangeStartOfParent + 1), - }); + // if (parent.type === 'ternary') + if (textTokens.find((token) => token.type === 'Text')!.body === '') { + textTokens.push({ + type: 'Text', + range: [rangeStartOfParent, temporaryRangeEnd], + body: formattedText.slice(rangeStartOfParent, temporaryRangeEnd), + }); + textTokens.push({ + type: 'Placeholder', + range: [0, rangeStartOfParent], + body: formattedText.slice(0, rangeStartOfParent), + }); + } else { + textTokens.push({ + type: 'Text', + range: [rangeStartOfParent + 1, temporaryRangeEnd], + body: formattedText.slice(rangeStartOfParent + 1, temporaryRangeEnd), + }); + textTokens.push({ + type: 'Placeholder', + range: [0, rangeStartOfParent + 1], + body: formattedText.slice(0, rangeStartOfParent + 1), + }); + } } else { textTokens.push({ type: 'Text', @@ -279,7 +334,11 @@ function formatTokens( for (let tokenIndex = formattedTokens.length - 1; tokenIndex >= 0; tokenIndex -= 1) { const token = formattedTokens[tokenIndex]; - if (token.type === 'attribute') { + if (token.type === 'ternary') { + if (token.children?.length) { + token.children = formatTokens(token.children, indentUnit, options); + } + } else if (token.type === 'attribute') { const props = token.props!; const leadingText = isEndingPositionAbsolute ? assembleTokens(formattedTokens.slice(0, tokenIndex)) @@ -432,25 +491,33 @@ function unfreezeToken(token: TextToken): string { for (let index = token.children.length - 1; index >= 0; index -= 1) { const tokenOfChildren = token.children[index]; - if (tokenOfChildren.type === 'expression' && tokenOfChildren.frozenClassName !== undefined) { - const props = tokenOfChildren.props as Omit & { - indentLevel: number; - }; - const originalDelimiter = - // eslint-disable-next-line no-nested-ternary - props.delimiterType === 'single-quote' - ? SINGLE_QUOTE - : props.delimiterType === 'double-quote' - ? DOUBLE_QUOTE - : BACKTICK; - - // eslint-disable-next-line no-param-reassign - token.body = token.body.replace( - `${originalDelimiter}${tokenOfChildren.frozenClassName}${originalDelimiter}`, - `${token.children[index - 1].body}${unfreezeToken(tokenOfChildren)}${ - token.children[index + 1].body - }`, - ); + if (tokenOfChildren.frozenClassName !== undefined) { + if (tokenOfChildren.type === 'ternary') { + // eslint-disable-next-line no-param-reassign + token.body = token.body.replace( + tokenOfChildren.frozenClassName, + unfreezeToken(tokenOfChildren), + ); + } else if (tokenOfChildren.type === 'expression') { + const props = tokenOfChildren.props as Omit & { + indentLevel: number; + }; + const originalDelimiter = + // eslint-disable-next-line no-nested-ternary + props.delimiterType === 'single-quote' + ? SINGLE_QUOTE + : props.delimiterType === 'double-quote' + ? DOUBLE_QUOTE + : BACKTICK; + + // eslint-disable-next-line no-param-reassign + token.body = token.body.replace( + `${originalDelimiter}${tokenOfChildren.frozenClassName}${originalDelimiter}`, + `${token.children[index - 1].body}${unfreezeToken(tokenOfChildren)}${ + token.children[index + 1].body + }`, + ); + } } } } @@ -464,9 +531,7 @@ export function parseAndAssemble( targetClassNameNodes: ClassNameNode[], options: ResolvedOptions, ): string { - const structuredClassNameNodes = structuringClassNameNodes( - targetClassNameNodes.filter(({ type }) => type !== 'ternary') as NonTernaryNode[], - ); + const structuredClassNameNodes = structuringClassNameNodes(targetClassNameNodes); const textTokens = linearParse(formattedText, indentUnit, structuredClassNameNodes); diff --git a/src/packages/core-parts/shared.ts b/src/packages/core-parts/shared.ts index 880b98d..37ae993 100644 --- a/src/packages/core-parts/shared.ts +++ b/src/packages/core-parts/shared.ts @@ -88,7 +88,7 @@ export type ExpressionNode = ClassNameNodeBase & { /** * In fact, the ternary operator itself is not a class name node, but it defines a type as an exception because it needs to be frozen when processing complex expressions. */ -export type TernaryExpressionNode = ClassNameNodeBase & { +type TernaryExpressionNode = ClassNameNodeBase & { type: 'ternary'; }; From 163046284719798e1cabdafa3aa6f55daebf2f38 Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sat, 2 Nov 2024 11:48:10 +0900 Subject: [PATCH 19/20] feat: make output consistent for nested expressions --- src/packages/core-parts/experimental.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 142294d..7c4d0f6 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -259,7 +259,7 @@ function linearParse( }; if (parent) { - const frozenClassName = freezeClassName(classNameWithoutDelimiter); + const frozenClassName = freezeClassName(classNameWithoutDelimiter.replace(/\s+/g, SPACE)); classNameToken.frozenClassName = frozenClassName; } From 304d52430967eea70c132d477b73e6d241d9263b Mon Sep 17 00:00:00 2001 From: Hyeonjong Date: Sat, 2 Nov 2024 12:28:15 +0900 Subject: [PATCH 20/20] refactor: rename freezing function --- src/packages/core-parts/experimental.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/packages/core-parts/experimental.ts b/src/packages/core-parts/experimental.ts index 7c4d0f6..73a96c5 100644 --- a/src/packages/core-parts/experimental.ts +++ b/src/packages/core-parts/experimental.ts @@ -7,7 +7,7 @@ function sha1(input: string): string { return createHash('sha1').update(input).digest('hex'); } -function freezeClassName(input: string): string { +function freezeText(input: string): string { const charCodeForLowerCaseAlpha = 945; const greekPlaceholder = [...Array(16)].map((_, index) => String.fromCharCode(charCodeForLowerCaseAlpha + index), @@ -218,7 +218,7 @@ function linearParse( }; if (parent) { - const frozenClassName = freezeClassName(ternaryExpression); + const frozenClassName = freezeText(ternaryExpression); ternaryToken.frozenClassName = frozenClassName; } @@ -259,7 +259,7 @@ function linearParse( }; if (parent) { - const frozenClassName = freezeClassName(classNameWithoutDelimiter.replace(/\s+/g, SPACE)); + const frozenClassName = freezeText(classNameWithoutDelimiter.replace(/\s+/g, SPACE)); classNameToken.frozenClassName = frozenClassName; }