From 0ae7f925e3d3e68d0d6680c9ab30c6dbdda98b2d Mon Sep 17 00:00:00 2001 From: Isaac Lee <16869656+ijlee2@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:58:54 +0200 Subject: [PATCH 1/5] refactor: Renamed file --- src/parse/index.ts | 2 +- src/print/index.ts | 2 +- src/utils/{index.ts => assert.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/utils/{index.ts => assert.ts} (100%) diff --git a/src/parse/index.ts b/src/parse/index.ts index 1a1abb27..ecb0f2de 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -12,7 +12,7 @@ import { parsers as babelParsers } from 'prettier/plugins/babel.js'; import { PRINTER_NAME } from '../config.js'; import type { Options } from '../options.js'; -import { assert } from '../utils/index.js'; +import { assert } from '../utils/assert.js'; import { byteToCharIndex, preprocessTemplateRange, diff --git a/src/print/index.ts b/src/print/index.ts index 110fd038..30b39901 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -13,7 +13,7 @@ import { isGlimmerTemplate, isGlimmerTemplateParent, } from '../types/glimmer.js'; -import { assert } from '../utils/index.js'; +import { assert } from '../utils/assert.js'; import { fixPreviousPrint, saveCurrentPrintOnSiblingNode, diff --git a/src/utils/index.ts b/src/utils/assert.ts similarity index 100% rename from src/utils/index.ts rename to src/utils/assert.ts From ddee426ab9ef1c064d761d87f03c60f6f92779a0 Mon Sep 17 00:00:00 2001 From: Isaac Lee <16869656+ijlee2@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:58:58 +0200 Subject: [PATCH 2/5] refactor: Separated concerns --- src/parse/index.ts | 46 +---------------------------- src/parse/preprocess.ts | 42 ++++++++++++++++++++++++++ tests/unit-tests/preprocess.test.ts | 2 +- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/parse/index.ts b/src/parse/index.ts index ecb0f2de..fd6bbd9c 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -6,21 +6,15 @@ import type { ObjectExpression, StaticBlock, } from '@babel/types'; -import { Preprocessor } from 'content-tag'; import type { Parser } from 'prettier'; import { parsers as babelParsers } from 'prettier/plugins/babel.js'; import { PRINTER_NAME } from '../config.js'; import type { Options } from '../options.js'; import { assert } from '../utils/assert.js'; -import { - byteToCharIndex, - preprocessTemplateRange, - type Template, -} from './preprocess.js'; +import { preprocess, type Template } from './preprocess.js'; const typescript = babelParsers['babel-ts'] as Parser; -const p = new Preprocessor(); /** Converts a node into a GlimmerTemplate node */ function convertNode( @@ -87,27 +81,6 @@ function convertAst(ast: File, templates: Template[]): void { } } -/** - * Pre-processes the template info, parsing the template content to Glimmer AST, - * fixing the offsets and locations of all nodes also calculates the block - * params locations & ranges and adding it to the info - */ -export function preprocess( - code: string, - fileName: string, -): { - code: string; - templates: Template[]; -} { - const templates = codeToGlimmerAst(code, fileName); - - for (const template of templates) { - code = preprocessTemplateRange(template, code); - } - - return { templates, code }; -} - export const parser: Parser = { ...typescript, astFormat: PRINTER_NAME, @@ -120,20 +93,3 @@ export const parser: Parser = { return ast; }, }; - -/** Pre-processes the template info, parsing the template content to Glimmer AST. */ -export function codeToGlimmerAst(code: string, filename: string): Template[] { - const rawTemplates = p.parse(code, { filename }); - const templates: Template[] = rawTemplates.map((r) => ({ - type: r.type, - range: r.range, - contentRange: r.contentRange, - contents: r.contents, - utf16Range: { - start: byteToCharIndex(code, r.range.start), - end: byteToCharIndex(code, r.range.end), - }, - })); - - return templates; -} diff --git a/src/parse/preprocess.ts b/src/parse/preprocess.ts index 0dd434a8..89a725b4 100644 --- a/src/parse/preprocess.ts +++ b/src/parse/preprocess.ts @@ -1,3 +1,5 @@ +import { Preprocessor } from 'content-tag'; + export interface Template { contents: string; type: string; @@ -87,3 +89,43 @@ export function preprocessTemplateRange( const total = prefix + content + ' '.repeat(spaces) + suffix; return replaceRange(code, template.range.start, template.range.end, total); } + +const p = new Preprocessor(); + +/** Pre-processes the template info, parsing the template content to Glimmer AST. */ +export function codeToGlimmerAst(code: string, filename: string): Template[] { + const rawTemplates = p.parse(code, { filename }); + const templates: Template[] = rawTemplates.map((r) => ({ + type: r.type, + range: r.range, + contentRange: r.contentRange, + contents: r.contents, + utf16Range: { + start: byteToCharIndex(code, r.range.start), + end: byteToCharIndex(code, r.range.end), + }, + })); + + return templates; +} + +/** + * Pre-processes the template info, parsing the template content to Glimmer AST, + * fixing the offsets and locations of all nodes also calculates the block + * params locations & ranges and adding it to the info + */ +export function preprocess( + code: string, + fileName: string, +): { + code: string; + templates: Template[]; +} { + const templates = codeToGlimmerAst(code, fileName); + + for (const template of templates) { + code = preprocessTemplateRange(template, code); + } + + return { templates, code }; +} diff --git a/tests/unit-tests/preprocess.test.ts b/tests/unit-tests/preprocess.test.ts index 05bec1a8..6ab6f1f9 100644 --- a/tests/unit-tests/preprocess.test.ts +++ b/tests/unit-tests/preprocess.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest'; -import { codeToGlimmerAst } from '../../src/parse/index.js'; import { + codeToGlimmerAst, PLACEHOLDER, preprocessTemplateRange, } from '../../src/parse/preprocess.js'; From ed2566d2d5fdd478a2d91d5e6a353dd7c4234899 Mon Sep 17 00:00:00 2001 From: Isaac Lee <16869656+ijlee2@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:59:02 +0200 Subject: [PATCH 3/5] refactor: Removed unnecessary exports --- src/parse/preprocess.ts | 8 ++++---- tests/unit-tests/preprocess.test.ts | 11 ++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/parse/preprocess.ts b/src/parse/preprocess.ts index 89a725b4..3574d6a1 100644 --- a/src/parse/preprocess.ts +++ b/src/parse/preprocess.ts @@ -15,7 +15,7 @@ export interface Template { const BufferMap: Map = new Map(); -export const PLACEHOLDER = '~'; +const PLACEHOLDER = '~'; function getBuffer(s: string): Buffer { let buf = BufferMap.get(s); @@ -27,19 +27,19 @@ function getBuffer(s: string): Buffer { } /** Slice string using byte range */ -export function sliceByteRange(s: string, a: number, b?: number): string { +function sliceByteRange(s: string, a: number, b?: number): string { const buf = getBuffer(s); return buf.subarray(a, b).toString(); } /** Converts byte index to js char index (utf16) */ -export function byteToCharIndex(s: string, byteOffset: number): number { +function byteToCharIndex(s: string, byteOffset: number): number { const buf = getBuffer(s); return buf.subarray(0, byteOffset).toString().length; } /** Calculate byte length */ -export function byteLength(s: string): number { +function byteLength(s: string): number { return getBuffer(s).length; } diff --git a/tests/unit-tests/preprocess.test.ts b/tests/unit-tests/preprocess.test.ts index 6ab6f1f9..f516c030 100644 --- a/tests/unit-tests/preprocess.test.ts +++ b/tests/unit-tests/preprocess.test.ts @@ -2,7 +2,6 @@ import { describe, expect, test } from 'vitest'; import { codeToGlimmerAst, - PLACEHOLDER, preprocessTemplateRange, } from '../../src/parse/preprocess.js'; @@ -13,21 +12,19 @@ const TEST_CASES = [ }, { code: '', - expected: [`{/*${PLACEHOLDER}* hi *${PLACEHOLDER} */}`], + expected: [`{/*~* hi *~ */}`], }, { code: '', - expected: [`{/*
hi<${PLACEHOLDER}div> */}`], + expected: [`{/*
hi<~div> */}`], }, { code: '', - expected: [`{/*{{#if true}}hi{{${PLACEHOLDER}if}} */}`], + expected: [`{/*{{#if true}}hi{{~if}} */}`], }, { code: '', - expected: [ - `{/*${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER}${PLACEHOLDER} */}`, - ], + expected: [`{/*~~~~~~~~~~~~~~~~ */}`], }, { code: '', From aa860e24c8c6006723a5c1bf453aa86d055b05d6 Mon Sep 17 00:00:00 2001 From: Isaac Lee <16869656+ijlee2@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:59:06 +0200 Subject: [PATCH 4/5] refactor: Simplified traverse() method --- src/parse/index.ts | 49 ++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/parse/index.ts b/src/parse/index.ts index fd6bbd9c..8179a6c1 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -30,29 +30,34 @@ function convertNode( /** Traverses the AST and replaces the transformed template parts with other AST */ function convertAst(ast: File, templates: Template[]): void { - const unprocessedTemplates = [...templates]; - traverse(ast, { enter(path) { const { node } = path; - if ( - node.type === 'BlockStatement' || - node.type === 'ObjectExpression' || - node.type === 'StaticBlock' - ) { - const { range } = node; - assert('expected range', range); - const [start, end] = range; + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check + switch (node.type) { + case 'BlockStatement': + case 'ObjectExpression': + case 'StaticBlock': { + assert('expected range', node.range); + const [start, end] = node.range; + + const templateIndex = templates.findIndex((template) => { + const { utf16Range } = template; + + if (utf16Range.start === start && utf16Range.end === end) { + return true; + } - const templateIndex = unprocessedTemplates.findIndex( - (t) => - (t.utf16Range.start === start && t.utf16Range.end === end) || - (node.type === 'ObjectExpression' && - t.utf16Range.start === start - 1 && - t.utf16Range.end === end + 1), - ); - if (templateIndex > -1) { - const rawTemplate = unprocessedTemplates.splice(templateIndex, 1)[0]; + return ( + node.type === 'ObjectExpression' && + utf16Range.start === start - 1 && + utf16Range.end === end + 1 + ); + }); + if (templateIndex === -1) { + return null; + } + const rawTemplate = templates.splice(templateIndex, 1)[0]; if (!rawTemplate) { throw new Error( 'expected raw template because splice index came from findIndex', @@ -65,8 +70,6 @@ function convertAst(ast: File, templates: Template[]): void { ast.comments.splice(index, 1); } convertNode(node, rawTemplate); - } else { - return null; } } @@ -74,9 +77,9 @@ function convertAst(ast: File, templates: Template[]): void { }, }); - if (unprocessedTemplates.length > 0) { + if (templates.length > 0) { throw new Error( - `failed to process all templates, ${unprocessedTemplates.length} remaining`, + `failed to process all templates, ${templates.length} remaining`, ); } } From 68083ae332a6309b332881101ce8914ccac10e80 Mon Sep 17 00:00:00 2001 From: Isaac Lee <16869656+ijlee2@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:59:10 +0200 Subject: [PATCH 5/5] refactor: Added new lines for readability --- src/parse/index.ts | 9 +++++++++ src/parse/preprocess.ts | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/src/parse/index.ts b/src/parse/index.ts index 8179a6c1..c8ef90cc 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -33,6 +33,7 @@ function convertAst(ast: File, templates: Template[]): void { traverse(ast, { enter(path) { const { node } = path; + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (node.type) { case 'BlockStatement': @@ -54,21 +55,27 @@ function convertAst(ast: File, templates: Template[]): void { utf16Range.end === end + 1 ); }); + if (templateIndex === -1) { return null; } + const rawTemplate = templates.splice(templateIndex, 1)[0]; + if (!rawTemplate) { throw new Error( 'expected raw template because splice index came from findIndex', ); } + const index = node.innerComments?.[0] && ast.comments?.indexOf(node.innerComments[0]); + if (ast.comments && index !== undefined && index >= 0) { ast.comments.splice(index, 1); } + convertNode(node, rawTemplate); } } @@ -90,9 +97,11 @@ export const parser: Parser = { async parse(code: string, options: Options): Promise { const preprocessed = preprocess(code, options.filepath); + const ast = await typescript.parse(preprocessed.code, options); assert('expected ast', ast); convertAst(ast as File, preprocessed.templates); + return ast; }, }; diff --git a/src/parse/preprocess.ts b/src/parse/preprocess.ts index 3574d6a1..bfcab46b 100644 --- a/src/parse/preprocess.ts +++ b/src/parse/preprocess.ts @@ -73,6 +73,7 @@ export function preprocessTemplateRange( suffix = '*/}'; const nextToken = code.slice(template.range.end).toString().match(/\S+/); + if (nextToken && (nextToken[0] === 'as' || nextToken[0] === 'satisfies')) { // Replace with parenthesized ObjectExpression prefix = '(' + prefix; @@ -83,10 +84,12 @@ export function preprocessTemplateRange( // We need to replace forward slash with _something else_, because // forward slash breaks the parsed templates. const content = template.contents.replaceAll('/', PLACEHOLDER); + const tplLength = template.range.end - template.range.start; const spaces = tplLength - byteLength(content) - prefix.length - suffix.length; const total = prefix + content + ' '.repeat(spaces) + suffix; + return replaceRange(code, template.range.start, template.range.end, total); } @@ -95,6 +98,7 @@ const p = new Preprocessor(); /** Pre-processes the template info, parsing the template content to Glimmer AST. */ export function codeToGlimmerAst(code: string, filename: string): Template[] { const rawTemplates = p.parse(code, { filename }); + const templates: Template[] = rawTemplates.map((r) => ({ type: r.type, range: r.range,