From d95a4d68dd74bcdb1d8581b6cb627eff65b5a7bb Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 15:42:12 -0800 Subject: [PATCH 01/32] Remove unnecessary Path type --- src/parse.ts | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index 9312115c..ba0fb6cb 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -12,13 +12,6 @@ import { assert } from './utils'; const typescript = babelParsers['babel-ts'] as Parser; const p = new Preprocessor(); -interface Path { - node: Node; - parent: Node | null; - parentKey: string | null; - parentPath: Path | null; -} - export interface TemplateNode { type: 'FunctionDeclaration'; leadingComments: Comment[]; @@ -54,7 +47,7 @@ function convertAst( let counter = 0; traverse(result.ast, { - enter(path: Path) { + enter(path) { const node = path.node; if ( node.type === 'ObjectExpression' || @@ -79,16 +72,16 @@ function convertAst( counter++; const ast = template.ast as TemplateNode; ast.extra.isAlreadyExportDefault = - path.parent?.type === 'ExportDefaultDeclaration' || - path.parentPath?.parent?.type === 'ExportDefaultDeclaration'; + path.parent.type === 'ExportDefaultDeclaration' || + path.parentPath?.parent.type === 'ExportDefaultDeclaration'; ast.extra.isDefaultTemplate = - path.parent?.type === 'ExportDefaultDeclaration' || - path.parent?.type === 'Program' || - (path.parent?.type === 'ExpressionStatement' && - path.parentPath?.parent?.type === 'Program') || - (path.parent?.type === 'TSAsExpression' && - path.parentPath?.parentPath?.parent?.type === 'Program') || - path.parentPath?.parent?.type === 'ExportDefaultDeclaration'; + path.parent.type === 'ExportDefaultDeclaration' || + path.parent.type === 'Program' || + (path.parent.type === 'ExpressionStatement' && + path.parentPath?.parent.type === 'Program') || + (path.parent.type === 'TSAsExpression' && + path.parentPath?.parentPath?.parent.type === 'Program') || + path.parentPath?.parent.type === 'ExportDefaultDeclaration'; ast.extra.isAssignment = !ast.extra.isDefaultTemplate && node.type !== 'StaticBlock'; From 488bf683fcc766b8e167eb3e64c5ee85e2da4370 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 16:05:13 -0800 Subject: [PATCH 02/32] Clean up parse.ts --- .eslintrc.js | 1 + src/parse.ts | 148 ++++++++++++++++++------------------------- src/print/index.ts | 4 +- src/types/glimmer.ts | 16 +++++ 4 files changed, 79 insertions(+), 90 deletions(-) create mode 100644 src/types/glimmer.ts diff --git a/.eslintrc.js b/.eslintrc.js index 8eb68d7e..ba6e31c0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -45,6 +45,7 @@ module.exports = { '@typescript-eslint/no-dynamic-delete': 'error', '@typescript-eslint/no-extra-semi': 'error', '@typescript-eslint/no-extraneous-class': 'error', + '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/no-invalid-void-type': 'error', '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-unnecessary-condition': 'error', diff --git a/src/parse.ts b/src/parse.ts index ba0fb6cb..572f6f0b 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -7,43 +7,52 @@ import { parsers as babelParsers } from 'prettier/plugins/babel.js'; import { PRINTER_NAME } from './config'; import type { Options } from './options.js'; +import type { GlimmerTemplate } from './types/glimmer'; import { assert } from './utils'; const typescript = babelParsers['babel-ts'] as Parser; const p = new Preprocessor(); -export interface TemplateNode { - type: 'FunctionDeclaration'; - leadingComments: Comment[]; - range: [number, number]; - start: number; - end: number; - extra: { - isGlimmerTemplate: boolean; - isDefaultTemplate: boolean; - isAssignment: boolean; - isAlreadyExportDefault: boolean; - template: string; - }; +interface Prepared { + output: string; + templateInfos: { + type: 'expression' | 'class-member'; + tagName: 'template'; + contents: string; + range: { + start: number; + end: number; + }; + contentRange: { + start: number; + end: number; + }; + startRange: { + end: number; + start: number; + }; + endRange: { + start: number; + end: number; + }; + }[]; } interface PreprocessedResult { - templateVisitorKeys: Record; - templateInfos: { - /** Range of the template including tags */ - templateRange: [number, number]; - /** Range of the template content, excluding tags */ - range: [number, number]; - ast: TemplateNode | undefined; - }[]; + /** Range of the template including tags */ + templateRange: readonly [start: number, end: number]; + /** Range of the template content, excluding tags */ + range: readonly [start: number, end: number]; + ast: GlimmerTemplate; } /** Traverses the AST and replaces the transformed template parts with other AST */ function convertAst( result: { ast: Node; code: string }, - preprocessedResult: PreprocessedResult, + preprocessedResult: PreprocessedResult[], ): void { - const templateInfos = preprocessedResult.templateInfos; + // FIXME: + const templateInfos = preprocessedResult; let counter = 0; traverse(result.ast, { @@ -70,7 +79,8 @@ function convertAst( return null; } counter++; - const ast = template.ast as TemplateNode; + assert('expected ast on template', template.ast); + const { ast } = template; ast.extra.isAlreadyExportDefault = path.parent.type === 'ExportDefaultDeclaration' || path.parentPath?.parent.type === 'ExportDefaultDeclaration'; @@ -98,69 +108,35 @@ function convertAst( } } -interface Info { - output: string; - templateInfos: { - type: 'expression' | 'class-member'; - tagName: 'template'; - contents: string; - range: { - start: number; - end: number; - }; - contentRange: { - start: number; - end: number; - }; - startRange: { - end: number; - start: number; - }; - endRange: { - start: number; - end: number; - }; - }[]; -} - /** * Preprocesses 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 */ -function preprocessGlimmerTemplates( - info: Info, - code: string, -): PreprocessedResult { - const templateInfos = info.templateInfos.map((r) => ({ - range: [r.contentRange.start, r.contentRange.end] as [number, number], - templateRange: [r.range.start, r.range.end] as [number, number], - ast: undefined as undefined | TemplateNode, - })); - const templateVisitorKeys = {}; - for (const tpl of templateInfos) { - const range = tpl.range; +function preprocess(info: Prepared, code: string): PreprocessedResult[] { + return info.templateInfos.map((tpl) => { + const range = [tpl.contentRange.start, tpl.contentRange.end] as const; + const templateRange = [tpl.range.start, tpl.range.end] as const; const template = code.slice(...range); - const ast: TemplateNode = { - type: 'FunctionDeclaration', - leadingComments: [], - range: [tpl.templateRange[0], tpl.templateRange[1]], - start: tpl.templateRange[0], - end: tpl.templateRange[1], - extra: { - isGlimmerTemplate: true, - isDefaultTemplate: false, - isAssignment: false, - isAlreadyExportDefault: false, - template, + return { + templateRange, + range, + ast: { + type: 'FunctionDeclaration', + leadingComments: [], + range: [templateRange[0], templateRange[1]], + start: templateRange[0], + end: templateRange[1], + extra: { + isGlimmerTemplate: true, + isDefaultTemplate: false, + isAssignment: false, + isAlreadyExportDefault: false, + template, + }, }, }; - tpl.ast = ast; - } - return { - templateVisitorKeys, - templateInfos, - }; + }); } function replaceRange( @@ -172,9 +148,9 @@ function replaceRange( return s.slice(0, start) + substitute + s.slice(end); } -function transformForPrettier(code: string): Info { +function prepare(code: string): Prepared { let jsCode = code; - const result = p.parse(code) as Info['templateInfos']; + const result = p.parse(code) as Prepared['templateInfos']; for (const tplInfo of result.reverse()) { const lineBreaks = [...tplInfo.contents].reduce( (previous, current) => previous + (current === '\n' ? 1 : 0), @@ -223,14 +199,10 @@ export const parser: Parser = { ...typescript, astFormat: PRINTER_NAME, - preprocess(text: string): string { - return text; - }, - async parse(code: string, options: Options): Promise { - const info = transformForPrettier(code); - const ast = await typescript.parse(info.output, options); - const preprocessedResult = preprocessGlimmerTemplates(info, code); + const prepared = prepare(code); + const preprocessedResult = preprocess(prepared, code); + const ast = await typescript.parse(prepared.output, options); assert('expected ast', ast); convertAst({ ast, code }, preprocessedResult); return ast; diff --git a/src/print/index.ts b/src/print/index.ts index 46f42d76..01b0e074 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -4,7 +4,7 @@ import type { AstPath } from 'prettier'; import { printers as estreePrinters } from 'prettier/plugins/estree.js'; import type { Options } from '../options.js'; -import type { TemplateNode } from '../parse'; +import type { GlimmerTemplate } from '../types/glimmer'; import { assert } from '../utils'; import { printTemplateContent, printTemplateTag } from './template'; @@ -162,7 +162,7 @@ export const printer: Printer = { content = node.extra['template'] as string; raw = true; } - const extra = node.extra as TemplateNode['extra']; + const extra = node.extra as GlimmerTemplate['extra']; const { isDefaultTemplate, isAssignment, isAlreadyExportDefault } = extra; const useHardline = !isAssignment || isDefaultTemplate || false; diff --git a/src/types/glimmer.ts b/src/types/glimmer.ts new file mode 100644 index 00000000..0e39b39d --- /dev/null +++ b/src/types/glimmer.ts @@ -0,0 +1,16 @@ +import { type Comment } from '@babel/types'; + +export interface GlimmerTemplate { + type: 'FunctionDeclaration'; + leadingComments: Comment[]; + range: [start: number, end: number]; + start: number; + end: number; + extra: { + isAlreadyExportDefault: boolean; + isAssignment: boolean; + isDefaultTemplate: boolean; + isGlimmerTemplate: boolean; + template: string; + }; +} From e0a1332d328e8c305def2ecd01363878bf0eed21 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 16:44:11 -0800 Subject: [PATCH 03/32] More parse.ts cleanup --- src/parse.ts | 99 +++++++++++++++++--------------------------- src/types/glimmer.ts | 34 +++++++++++++++ 2 files changed, 73 insertions(+), 60 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index 572f6f0b..56c6592e 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -7,7 +7,7 @@ import { parsers as babelParsers } from 'prettier/plugins/babel.js'; import { PRINTER_NAME } from './config'; import type { Options } from './options.js'; -import type { GlimmerTemplate } from './types/glimmer'; +import type { GlimmerTemplate, RawGlimmerTemplate } from './types/glimmer'; import { assert } from './utils'; const typescript = babelParsers['babel-ts'] as Parser; @@ -15,27 +15,7 @@ const p = new Preprocessor(); interface Prepared { output: string; - templateInfos: { - type: 'expression' | 'class-member'; - tagName: 'template'; - contents: string; - range: { - start: number; - end: number; - }; - contentRange: { - start: number; - end: number; - }; - startRange: { - end: number; - start: number; - }; - endRange: { - start: number; - end: number; - }; - }[]; + templateNodes: RawGlimmerTemplate[]; } interface PreprocessedResult { @@ -51,8 +31,6 @@ function convertAst( result: { ast: Node; code: string }, preprocessedResult: PreprocessedResult[], ): void { - // FIXME: - const templateInfos = preprocessedResult; let counter = 0; traverse(result.ast, { @@ -65,7 +43,7 @@ function convertAst( ) { const range = node.range as [number, number]; - const template = templateInfos.find( + const template = preprocessedResult.find( (t) => (t.templateRange[0] === range[0] && t.templateRange[1] === range[1]) || @@ -103,7 +81,7 @@ function convertAst( }, }); - if (counter !== templateInfos.length) { + if (counter !== preprocessedResult.length) { throw new Error('failed to process all templates'); } } @@ -114,7 +92,7 @@ function convertAst( * params locations & ranges and adding it to the info */ function preprocess(info: Prepared, code: string): PreprocessedResult[] { - return info.templateInfos.map((tpl) => { + return info.templateNodes.map((tpl) => { const range = [tpl.contentRange.start, tpl.contentRange.end] as const; const templateRange = [tpl.range.start, tpl.range.end] as const; const template = code.slice(...range); @@ -148,50 +126,51 @@ function replaceRange( return s.slice(0, start) + substitute + s.slice(end); } +const STATIC_OPEN = 'static{`'; +const STATIC_CLOSE = '`}'; +const NEWLINE = '\n'; + +/** FIXME: What is the purpose of this? */ function prepare(code: string): Prepared { - let jsCode = code; - const result = p.parse(code) as Prepared['templateInfos']; - for (const tplInfo of result.reverse()) { - const lineBreaks = [...tplInfo.contents].reduce( - (previous, current) => previous + (current === '\n' ? 1 : 0), - 0, - ); - if (tplInfo.type === 'class-member') { - const tplLength = tplInfo.range.end - tplInfo.range.start; - const spaces = tplLength - 'static{`'.length - '`}'.length - lineBreaks; - const total = ' '.repeat(spaces) + '\n'.repeat(lineBreaks); - const replacementCode = `static{\`${total}\`}`; - jsCode = replaceRange( - jsCode, - tplInfo.range.start, - tplInfo.range.end, - replacementCode, - ); + const templateNodes = p.parse(code) as RawGlimmerTemplate[]; + let output = code; + for (const templateNode of templateNodes.reverse()) { + let prefix: string; + let suffix: string; + if (templateNode.type === 'class-member') { + prefix = STATIC_OPEN; + suffix = STATIC_CLOSE; } else { - const tplLength = tplInfo.range.end - tplInfo.range.start; - const nextWord = code.slice(tplInfo.range.end).match(/\S+/); - let prefix = '{'; - let suffix = '}'; + const nextWord = code.slice(templateNode.range.end).match(/\S+/); + prefix = '{'; + suffix = '}'; if (nextWord && nextWord[0] === 'as') { prefix = '(' + prefix; suffix = suffix + ')'; } else if (!nextWord || ![',', ')'].includes(nextWord[0][0] || '')) { suffix += ';'; } - const spaces = tplLength - prefix.length - suffix.length - lineBreaks; - const total = ' '.repeat(spaces) + '\n'.repeat(lineBreaks); - const replacementCode = `${prefix}${total}${suffix}`; - jsCode = replaceRange( - jsCode, - tplInfo.range.start, - tplInfo.range.end, - replacementCode, - ); } + + const lineBreakCount = [...templateNode.contents].reduce( + (sum, currentContents) => sum + (currentContents === NEWLINE ? 1 : 0), + 0, + ); + const totalLength = templateNode.range.end - templateNode.range.start; + const spaces = totalLength - prefix.length - suffix.length - lineBreakCount; + const content = ' '.repeat(spaces) + NEWLINE.repeat(lineBreakCount); + + output = replaceRange( + output, + templateNode.range.start, + templateNode.range.end, + `${prefix}${content}${suffix}`, + ); } + return { - templateInfos: result, - output: jsCode, + templateNodes, + output, }; } diff --git a/src/types/glimmer.ts b/src/types/glimmer.ts index 0e39b39d..754845ae 100644 --- a/src/types/glimmer.ts +++ b/src/types/glimmer.ts @@ -1,5 +1,39 @@ import { type Comment } from '@babel/types'; +/** The raw GlimmerTemplate node as parsed by the content-tag parser. */ +export interface RawGlimmerTemplate { + type: 'expression' | 'class-member'; + tagName: 'template'; + /** Raw template contents */ + contents: string; + /** + * Range of the contents, inclusive of inclusive of the + * `` tags. + */ + range: { + start: number; + end: number; + }; + /** + * Range of the template contents, not inclusive of the + * `` tags. + */ + contentRange: { + start: number; + end: number; + }; + /** Range of the opening `` tag. */ + endRange: { + start: number; + end: number; + }; +} + export interface GlimmerTemplate { type: 'FunctionDeclaration'; leadingComments: Comment[]; From b293e056de44dd967c7d3b146d92ac87e2e0305c Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 16:51:54 -0800 Subject: [PATCH 04/32] More parse.ts cleanup --- src/parse.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index 56c6592e..9ca7a400 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -13,7 +13,7 @@ import { assert } from './utils'; const typescript = babelParsers['babel-ts'] as Parser; const p = new Preprocessor(); -interface Prepared { +interface PreparedResult { output: string; templateNodes: RawGlimmerTemplate[]; } @@ -87,12 +87,15 @@ function convertAst( } /** - * Preprocesses the template info, parsing the template content to Glimmer AST, + * 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 */ -function preprocess(info: Prepared, code: string): PreprocessedResult[] { - return info.templateNodes.map((tpl) => { +function preprocess( + prepared: PreparedResult, + code: string, +): PreprocessedResult[] { + return prepared.templateNodes.map((tpl) => { const range = [tpl.contentRange.start, tpl.contentRange.end] as const; const templateRange = [tpl.range.start, tpl.range.end] as const; const template = code.slice(...range); @@ -131,7 +134,7 @@ const STATIC_CLOSE = '`}'; const NEWLINE = '\n'; /** FIXME: What is the purpose of this? */ -function prepare(code: string): Prepared { +function prepare(code: string): PreparedResult { const templateNodes = p.parse(code) as RawGlimmerTemplate[]; let output = code; for (const templateNode of templateNodes.reverse()) { From c26162ff9f5a534c01ce1df37ec9e05b6f495aa3 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 16:57:05 -0800 Subject: [PATCH 05/32] Align range terminology --- src/parse.ts | 67 +++++++++++++++++++++++++------------------- src/types/glimmer.ts | 17 +++++++++++ 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index 9ca7a400..5d025f0d 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -19,10 +19,18 @@ interface PreparedResult { } interface PreprocessedResult { - /** Range of the template including tags */ - templateRange: readonly [start: number, end: number]; - /** Range of the template content, excluding tags */ + /** + * Range of the contents, inclusive of inclusive of the + * `` tags. + */ range: readonly [start: number, end: number]; + + /** + * Range of the template contents, not inclusive of the + * `` tags. + */ + contentRange: readonly [start: number, end: number]; + ast: GlimmerTemplate; } @@ -45,12 +53,9 @@ function convertAst( const template = preprocessedResult.find( (t) => - (t.templateRange[0] === range[0] && - t.templateRange[1] === range[1]) || - (t.templateRange[0] === range[0] - 1 && - t.templateRange[1] === range[1] + 1) || - (t.templateRange[0] === range[0] && - t.templateRange[1] === range[1] + 1), + (t.range[0] === range[0] && t.range[1] === range[1]) || + (t.range[0] === range[0] - 1 && t.range[1] === range[1] + 1) || + (t.range[0] === range[0] && t.range[1] === range[1] + 1), ); if (!template) { @@ -95,28 +100,32 @@ function preprocess( prepared: PreparedResult, code: string, ): PreprocessedResult[] { - return prepared.templateNodes.map((tpl) => { - const range = [tpl.contentRange.start, tpl.contentRange.end] as const; - const templateRange = [tpl.range.start, tpl.range.end] as const; - const template = code.slice(...range); - return { - templateRange, - range, - ast: { - type: 'FunctionDeclaration', - leadingComments: [], - range: [templateRange[0], templateRange[1]], - start: templateRange[0], - end: templateRange[1], - extra: { - isGlimmerTemplate: true, - isDefaultTemplate: false, - isAssignment: false, - isAlreadyExportDefault: false, - template, - }, + return prepared.templateNodes.map((templateNode) => { + const contentRange = [ + templateNode.contentRange.start, + templateNode.contentRange.end, + ] as const; + const range = [templateNode.range.start, templateNode.range.end] as const; + const template = code.slice(...contentRange); + const ast: GlimmerTemplate = { + type: 'FunctionDeclaration', + leadingComments: [], + range: [range[0], range[1]], + start: range[0], + end: range[1], + extra: { + isGlimmerTemplate: true, + isDefaultTemplate: false, + isAssignment: false, + isAlreadyExportDefault: false, + template, }, }; + return { + range, + contentRange, + ast, + } satisfies PreprocessedResult; }); } diff --git a/src/types/glimmer.ts b/src/types/glimmer.ts index 754845ae..df3f552a 100644 --- a/src/types/glimmer.ts +++ b/src/types/glimmer.ts @@ -3,9 +3,12 @@ import { type Comment } from '@babel/types'; /** The raw GlimmerTemplate node as parsed by the content-tag parser. */ export interface RawGlimmerTemplate { type: 'expression' | 'class-member'; + tagName: 'template'; + /** Raw template contents */ contents: string; + /** * Range of the contents, inclusive of inclusive of the * `` tags. @@ -14,6 +17,7 @@ export interface RawGlimmerTemplate { start: number; end: number; }; + /** * Range of the template contents, not inclusive of the * `` tags. @@ -22,11 +26,13 @@ export interface RawGlimmerTemplate { start: number; end: number; }; + /** Range of the opening `` tag. */ endRange: { start: number; @@ -36,10 +42,21 @@ export interface RawGlimmerTemplate { export interface GlimmerTemplate { type: 'FunctionDeclaration'; + leadingComments: Comment[]; + + /** + * Range of the contents, inclusive of inclusive of the + * `` tags. + */ range: [start: number, end: number]; + + /** Beginning of the range, before the opening `` tag. */ end: number; + extra: { isAlreadyExportDefault: boolean; isAssignment: boolean; From 8841439a78b407229cdc41087a530af4efd4ab37 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 17:21:32 -0800 Subject: [PATCH 06/32] Clean up convertAst --- .eslintrc.js | 2 ++ src/parse.ts | 68 +++++++++++++++++++------------------------- src/print/index.ts | 8 ++++-- src/types/glimmer.ts | 28 ++++++++++++++++-- 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ba6e31c0..64db77d9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -45,6 +45,7 @@ module.exports = { '@typescript-eslint/no-dynamic-delete': 'error', '@typescript-eslint/no-extra-semi': 'error', '@typescript-eslint/no-extraneous-class': 'error', + '@typescript-eslint/no-import-type-side-effects': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/no-invalid-void-type': 'error', '@typescript-eslint/no-require-imports': 'error', @@ -73,6 +74,7 @@ module.exports = { 'jsdoc/tag-lines': 'off', 'simple-import-sort/imports': 'error', 'simple-import-sort/exports': 'error', + 'unicorn/consistent-destructuring': 'off', 'unicorn/consistent-function-scoping': [ 'error', { checkArrowFunctions: false }, diff --git a/src/parse.ts b/src/parse.ts index 5d025f0d..17bec453 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,6 +1,5 @@ import { traverse } from '@babel/core'; -import type { Node } from '@babel/types'; -import { type Comment } from '@babel/types'; +import type { Comment, Node } from '@babel/types'; import { Preprocessor } from 'content-tag'; import type { Parser } from 'prettier'; import { parsers as babelParsers } from 'prettier/plugins/babel.js'; @@ -8,6 +7,7 @@ import { parsers as babelParsers } from 'prettier/plugins/babel.js'; import { PRINTER_NAME } from './config'; import type { Options } from './options.js'; import type { GlimmerTemplate, RawGlimmerTemplate } from './types/glimmer'; +import { isAlreadyExportDefault, isDefaultTemplate } from './types/glimmer'; import { assert } from './utils'; const typescript = babelParsers['babel-ts'] as Parser; @@ -31,56 +31,48 @@ interface PreprocessedResult { */ contentRange: readonly [start: number, end: number]; - ast: GlimmerTemplate; + templateNode: GlimmerTemplate; } /** Traverses the AST and replaces the transformed template parts with other AST */ -function convertAst( - result: { ast: Node; code: string }, - preprocessedResult: PreprocessedResult[], -): void { +function convertAst(ast: Node, preprocessedResult: PreprocessedResult[]): void { let counter = 0; - traverse(result.ast, { + traverse(ast, { enter(path) { - const node = path.node; + const { node } = path; if ( node.type === 'ObjectExpression' || node.type === 'BlockStatement' || node.type === 'StaticBlock' ) { - const range = node.range as [number, number]; - - const template = preprocessedResult.find( - (t) => - (t.range[0] === range[0] && t.range[1] === range[1]) || - (t.range[0] === range[0] - 1 && t.range[1] === range[1] + 1) || - (t.range[0] === range[0] && t.range[1] === range[1] + 1), + const { range } = node; + assert('expected range', range); + + const preprocessedTemplate = preprocessedResult.find( + (p) => + (p.range[0] === range[0] && p.range[1] === range[1]) || + (p.range[0] === range[0] - 1 && p.range[1] === range[1] + 1) || + (p.range[0] === range[0] && p.range[1] === range[1] + 1), ); - if (!template) { + if (!preprocessedTemplate) { return null; } + + const { templateNode } = preprocessedTemplate; + templateNode.extra.isAlreadyExportDefault = + isAlreadyExportDefault(path); + templateNode.extra.isDefaultTemplate = isDefaultTemplate(path); + + templateNode.extra.isAssignment = + !templateNode.extra.isDefaultTemplate && node.type !== 'StaticBlock'; + + templateNode.leadingComments = node.leadingComments as Comment[]; + + Object.assign(node, templateNode); + counter++; - assert('expected ast on template', template.ast); - const { ast } = template; - ast.extra.isAlreadyExportDefault = - path.parent.type === 'ExportDefaultDeclaration' || - path.parentPath?.parent.type === 'ExportDefaultDeclaration'; - ast.extra.isDefaultTemplate = - path.parent.type === 'ExportDefaultDeclaration' || - path.parent.type === 'Program' || - (path.parent.type === 'ExpressionStatement' && - path.parentPath?.parent.type === 'Program') || - (path.parent.type === 'TSAsExpression' && - path.parentPath?.parentPath?.parent.type === 'Program') || - path.parentPath?.parent.type === 'ExportDefaultDeclaration'; - - ast.extra.isAssignment = - !ast.extra.isDefaultTemplate && node.type !== 'StaticBlock'; - - ast.leadingComments = node.leadingComments as Comment[]; - Object.assign(node, ast); } return null; }, @@ -124,7 +116,7 @@ function preprocess( return { range, contentRange, - ast, + templateNode: ast, } satisfies PreprocessedResult; }); } @@ -195,7 +187,7 @@ export const parser: Parser = { const preprocessedResult = preprocess(prepared, code); const ast = await typescript.parse(prepared.output, options); assert('expected ast', ast); - convertAst({ ast, code }, preprocessedResult); + convertAst(ast, preprocessedResult); return ast; }, }; diff --git a/src/print/index.ts b/src/print/index.ts index 01b0e074..b0754297 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -1,6 +1,10 @@ import type { Node } from '@babel/types'; -import type { doc, Options as PrettierOptions, Printer } from 'prettier'; -import type { AstPath } from 'prettier'; +import type { + AstPath, + doc, + Options as PrettierOptions, + Printer, +} from 'prettier'; import { printers as estreePrinters } from 'prettier/plugins/estree.js'; import type { Options } from '../options.js'; diff --git a/src/types/glimmer.ts b/src/types/glimmer.ts index df3f552a..03360331 100644 --- a/src/types/glimmer.ts +++ b/src/types/glimmer.ts @@ -1,4 +1,5 @@ -import { type Comment } from '@babel/types'; +import type { NodePath } from '@babel/core'; +import type { Comment } from '@babel/types'; /** The raw GlimmerTemplate node as parsed by the content-tag parser. */ export interface RawGlimmerTemplate { @@ -58,10 +59,33 @@ export interface GlimmerTemplate { end: number; extra: { + isGlimmerTemplate: true; + // FIXME: Is this actually used? isAlreadyExportDefault: boolean; isAssignment: boolean; isDefaultTemplate: boolean; - isGlimmerTemplate: boolean; template: string; }; } + +/** Returns true if the GlimmerTemplate path is already a default export. */ +export function isAlreadyExportDefault(path: NodePath): boolean { + return ( + path.parent.type === 'ExportDefaultDeclaration' || + path.parentPath?.parent.type === 'ExportDefaultDeclaration' + ); +} + +// FIXME: Seems to overlap with isAlreadyExportDefault +/** Returns true if the GlimmerTemplate path is already a default template. */ +export function isDefaultTemplate(path: NodePath): boolean { + return ( + path.parent.type === 'ExportDefaultDeclaration' || + path.parent.type === 'Program' || + (path.parent.type === 'ExpressionStatement' && + path.parentPath?.parent.type === 'Program') || + (path.parent.type === 'TSAsExpression' && + path.parentPath?.parentPath?.parent.type === 'Program') || + path.parentPath?.parent.type === 'ExportDefaultDeclaration' + ); +} From 417fce23cd5c6fefb1f4a475105d6c4052b041f9 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 17:50:57 -0800 Subject: [PATCH 07/32] Start printer cleanup --- src/print/index.ts | 26 ++++++++++++-------------- src/types/glimmer.ts | 7 ++++++- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/print/index.ts b/src/print/index.ts index b0754297..2cfd22b5 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -8,7 +8,7 @@ import type { import { printers as estreePrinters } from 'prettier/plugins/estree.js'; import type { Options } from '../options.js'; -import type { GlimmerTemplate } from '../types/glimmer'; +import { isGlimmerTemplate } from '../types/glimmer'; import { assert } from '../utils'; import { printTemplateContent, printTemplateTag } from './template'; @@ -153,28 +153,27 @@ export const printer: Printer = { return async (textToDoc) => { try { - if (node?.extra?.['isGlimmerTemplate'] && node.extra['template']) { + if (node && isGlimmerTemplate(node)) { let content = null; let raw = false; try { content = await printTemplateContent( - node.extra['template'] as string, + node.extra.template, textToDoc, embedOptions as Options, ); } catch { - content = node.extra['template'] as string; + content = node.extra.template; raw = true; } - const extra = node.extra as GlimmerTemplate['extra']; - const { isDefaultTemplate, isAssignment, isAlreadyExportDefault } = - extra; - const useHardline = !isAssignment || isDefaultTemplate || false; + const { + extra: { isDefaultTemplate, isAssignment, isAlreadyExportDefault }, + } = node; + const useHardline = !isAssignment || isDefaultTemplate; const shouldExportDefault = - (!isAlreadyExportDefault && - isDefaultTemplate && - options.templateExportDefault) || - false; + !isAlreadyExportDefault && + isDefaultTemplate && + (options.templateExportDefault ?? false); const printed = printTemplateTag(content, { exportDefault: shouldExportDefault, useHardline, @@ -184,7 +183,6 @@ export const printer: Printer = { return printed; } } catch (error) { - console.log(error); const printed = [printRawText(path, embedOptions as Options)]; saveCurrentPrintOnSiblingNode(path, printed); return printed; @@ -197,7 +195,7 @@ export const printer: Printer = { /** * Turn off any built-in prettier-ignore handling because it will skip - * embedding, which will print `[__GLIMMER_TEMPLATE(...)]` instead of + * embedding, which will print the preprocessed template instead of * ``. */ hasPrettierIgnore: undefined, diff --git a/src/types/glimmer.ts b/src/types/glimmer.ts index 03360331..387ca135 100644 --- a/src/types/glimmer.ts +++ b/src/types/glimmer.ts @@ -1,5 +1,5 @@ import type { NodePath } from '@babel/core'; -import type { Comment } from '@babel/types'; +import { is, type Comment, type Node } from '@babel/types'; /** The raw GlimmerTemplate node as parsed by the content-tag parser. */ export interface RawGlimmerTemplate { @@ -68,6 +68,11 @@ export interface GlimmerTemplate { }; } +// @ts-expect-error FIXME: +export function isGlimmerTemplate(node: Node): node is GlimmerTemplate { + return node.extra?.['isGlimmerTemplate'] === true; +} + /** Returns true if the GlimmerTemplate path is already a default export. */ export function isAlreadyExportDefault(path: NodePath): boolean { return ( From 89e20bf4da28f7fb97d4f76907673b240aced033 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 17:54:34 -0800 Subject: [PATCH 08/32] More printer cleanup --- src/print/index.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/print/index.ts b/src/print/index.ts index 2cfd22b5..7adf1992 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -8,27 +8,27 @@ import type { import { printers as estreePrinters } from 'prettier/plugins/estree.js'; import type { Options } from '../options.js'; -import { isGlimmerTemplate } from '../types/glimmer'; +import { GlimmerTemplate, isGlimmerTemplate } from '../types/glimmer'; import { assert } from '../utils'; import { printTemplateContent, printTemplateTag } from './template'; const estreePrinter = estreePrinters['estree'] as Printer; -function getGlimmerExpression(node: Node | undefined): Node | null { +function getGlimmerTemplate(node: Node | undefined): GlimmerTemplate | null { if (!node) return null; - if (node.extra?.['isGlimmerTemplate']) { + if (isGlimmerTemplate(node)) { return node; } if ( node.type === 'ExportDefaultDeclaration' && - node.declaration.extra?.['isGlimmerTemplate'] + isGlimmerTemplate(node.declaration) ) { return node.declaration; } if ( node.type === 'ExportDefaultDeclaration' && node.declaration.type === 'TSAsExpression' && - node.declaration.expression.extra?.['isGlimmerTemplate'] + isGlimmerTemplate(node.declaration.expression) ) { return node.declaration.expression; } @@ -93,10 +93,7 @@ export const printer: Printer = { ...estreePrinter, getVisitorKeys(node, nonTraversableKeys) { - if (node === undefined) { - return []; - } - if (node.extra?.['isGlimmerTemplate']) { + if (!node || isGlimmerTemplate(node)) { return []; } return estreePrinter.getVisitorKeys?.(node, nonTraversableKeys) || []; @@ -110,7 +107,7 @@ export const printer: Printer = { ) { const { node } = path; const hasPrettierIgnore = checkPrettierIgnore(path); - if (getGlimmerExpression(node)) { + if (getGlimmerTemplate(node)) { if (hasPrettierIgnore) { return printRawText(path, options); } else { From 0e7542b6f039d3f0ec3f54fbb41f0d1954531f35 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 18:22:00 -0800 Subject: [PATCH 09/32] Clean up flattenDoc --- src/print/index.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/print/index.ts b/src/print/index.ts index 7adf1992..099e2739 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -35,16 +35,17 @@ function getGlimmerTemplate(node: Node | undefined): GlimmerTemplate | null { return null; } +/** NOTE: This is highly specialized for use in `fixPreviousPrint` */ function flattenDoc(doc: doc.builders.Doc): string[] { - const array = (doc as unknown as doc.builders.Group).contents || doc; - if (!Array.isArray(array)) return array as unknown as string[]; - return array.flatMap((x) => - (x as doc.builders.Group).contents - ? flattenDoc((x as doc.builders.Group).contents) - : Array.isArray(x) - ? flattenDoc(x) - : x, - ) as string[]; + if (Array.isArray(doc)) { + return doc.flatMap(flattenDoc); + } else if (typeof doc === 'string') { + return [doc]; + } else if ('contents' in doc) { + return flattenDoc(doc.contents); + } else { + return []; + } } /** From 7bc83650ba8504226188c4a34df8e79f46b85c2d Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 18:35:43 -0800 Subject: [PATCH 10/32] Fix export default bug --- examples/input/example.gts | 16 ++++++++------- src/parse.ts | 6 +----- src/print/index.ts | 20 +++++++++---------- src/print/template.ts | 4 ---- src/types/glimmer.ts | 13 ++---------- .../template-export-default.test.ts.snap | 6 +++--- 6 files changed, 25 insertions(+), 40 deletions(-) diff --git a/examples/input/example.gts b/examples/input/example.gts index 32f3cbdc..ccc7f50e 100644 --- a/examples/input/example.gts +++ b/examples/input/example.gts @@ -1,12 +1,14 @@ import type { TemplateOnlyComponent } from '@ember/component/template-only'; export interface Signature { - Element: HTMLElement, - Args: { - - - } - Yields: [] + Element: HTMLElement; + Args: {}; + Yields: []; } -export default as TemplateOnlyComponent \ No newline at end of file +export default as TemplateOnlyComponent; diff --git a/src/parse.ts b/src/parse.ts index 17bec453..ea4e4854 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -7,7 +7,7 @@ import { parsers as babelParsers } from 'prettier/plugins/babel.js'; import { PRINTER_NAME } from './config'; import type { Options } from './options.js'; import type { GlimmerTemplate, RawGlimmerTemplate } from './types/glimmer'; -import { isAlreadyExportDefault, isDefaultTemplate } from './types/glimmer'; +import { isDefaultTemplate } from './types/glimmer'; import { assert } from './utils'; const typescript = babelParsers['babel-ts'] as Parser; @@ -61,10 +61,7 @@ function convertAst(ast: Node, preprocessedResult: PreprocessedResult[]): void { } const { templateNode } = preprocessedTemplate; - templateNode.extra.isAlreadyExportDefault = - isAlreadyExportDefault(path); templateNode.extra.isDefaultTemplate = isDefaultTemplate(path); - templateNode.extra.isAssignment = !templateNode.extra.isDefaultTemplate && node.type !== 'StaticBlock'; @@ -109,7 +106,6 @@ function preprocess( isGlimmerTemplate: true, isDefaultTemplate: false, isAssignment: false, - isAlreadyExportDefault: false, template, }, }; diff --git a/src/print/index.ts b/src/print/index.ts index 099e2739..6b0ddf7f 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -8,7 +8,8 @@ import type { import { printers as estreePrinters } from 'prettier/plugins/estree.js'; import type { Options } from '../options.js'; -import { GlimmerTemplate, isGlimmerTemplate } from '../types/glimmer'; +import type { GlimmerTemplate } from '../types/glimmer'; +import { isGlimmerTemplate } from '../types/glimmer'; import { assert } from '../utils'; import { printTemplateContent, printTemplateTag } from './template'; @@ -116,14 +117,19 @@ export const printer: Printer = { assert('Expected Glimmer doc to be an array', Array.isArray(printed)); trimPrinted(printed); + // Always remove export default so we start with a blank slate if ( - !options.templateExportDefault && docMatchesString(printed[0], 'export') && docMatchesString(printed[1], 'default') ) { printed = printed.slice(2); trimPrinted(printed); } + + if (options.templateExportDefault) { + printed.unshift('export ', 'default '); + } + saveCurrentPrintOnSiblingNode(path, printed); return printed; } @@ -143,7 +149,6 @@ export const printer: Printer = { const { node } = path; const hasPrettierIgnore = checkPrettierIgnore(path); - const options = { ...embedOptions } as Options; if (hasPrettierIgnore) { return printRawText(path, embedOptions as Options); @@ -165,22 +170,17 @@ export const printer: Printer = { raw = true; } const { - extra: { isDefaultTemplate, isAssignment, isAlreadyExportDefault }, + extra: { isDefaultTemplate, isAssignment }, } = node; const useHardline = !isAssignment || isDefaultTemplate; - const shouldExportDefault = - !isAlreadyExportDefault && - isDefaultTemplate && - (options.templateExportDefault ?? false); const printed = printTemplateTag(content, { - exportDefault: shouldExportDefault, useHardline, raw, }); saveCurrentPrintOnSiblingNode(path, printed); return printed; } - } catch (error) { + } catch { const printed = [printRawText(path, embedOptions as Options)]; saveCurrentPrintOnSiblingNode(path, printed); return printed; diff --git a/src/print/template.ts b/src/print/template.ts index 5f3d54b9..ec28f60e 100644 --- a/src/print/template.ts +++ b/src/print/template.ts @@ -45,7 +45,6 @@ export async function printTemplateContent( export function printTemplateTag( content: doc.builders.Doc, options: { - exportDefault: boolean; useHardline: boolean; raw: boolean; }, @@ -57,9 +56,6 @@ export function printTemplateTag( line, TEMPLATE_TAG_CLOSE, ]; - if (options.exportDefault) { - doc.splice(0, 0, 'export default '); - } return [group(doc)]; } diff --git a/src/types/glimmer.ts b/src/types/glimmer.ts index 387ca135..082117d2 100644 --- a/src/types/glimmer.ts +++ b/src/types/glimmer.ts @@ -1,5 +1,5 @@ import type { NodePath } from '@babel/core'; -import { is, type Comment, type Node } from '@babel/types'; +import type { Comment, Node } from '@babel/types'; /** The raw GlimmerTemplate node as parsed by the content-tag parser. */ export interface RawGlimmerTemplate { @@ -60,27 +60,18 @@ export interface GlimmerTemplate { extra: { isGlimmerTemplate: true; - // FIXME: Is this actually used? - isAlreadyExportDefault: boolean; isAssignment: boolean; isDefaultTemplate: boolean; template: string; }; } +/** Returns true if the node is a GlimmerTemplate. */ // @ts-expect-error FIXME: export function isGlimmerTemplate(node: Node): node is GlimmerTemplate { return node.extra?.['isGlimmerTemplate'] === true; } -/** Returns true if the GlimmerTemplate path is already a default export. */ -export function isAlreadyExportDefault(path: NodePath): boolean { - return ( - path.parent.type === 'ExportDefaultDeclaration' || - path.parentPath?.parent.type === 'ExportDefaultDeclaration' - ); -} - // FIXME: Seems to overlap with isAlreadyExportDefault /** Returns true if the GlimmerTemplate path is already a default template. */ export function isDefaultTemplate(path: NodePath): boolean { diff --git a/tests/unit-tests/config/__snapshots__/template-export-default.test.ts.snap b/tests/unit-tests/config/__snapshots__/template-export-default.test.ts.snap index 64d5a494..feee5490 100644 --- a/tests/unit-tests/config/__snapshots__/template-export-default.test.ts.snap +++ b/tests/unit-tests/config/__snapshots__/template-export-default.test.ts.snap @@ -118,7 +118,7 @@ exports[`config > templateExportDefault: true > it formats ../cases/gjs/exported `; exports[`config > templateExportDefault: true > it formats ../cases/gjs/invalid-template.gjs 1`] = ` -"export default - + } " `; @@ -3223,9 +3217,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3243,9 +3235,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3356,9 +3346,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3582,9 +3570,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3602,9 +3588,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3717,9 +3701,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3943,9 +3925,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3962,9 +3942,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -4039,9 +4017,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; diff --git a/tests/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap b/tests/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap index 6ab6f4c0..912d43c7 100644 --- a/tests/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap +++ b/tests/unit-tests/ambiguous/__snapshots__/semi-false.test.ts.snap @@ -2861,9 +2861,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -2881,9 +2879,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -2996,9 +2992,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3223,9 +3217,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3243,9 +3235,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3356,9 +3346,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3582,9 +3570,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3602,9 +3588,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3717,9 +3701,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3943,9 +3925,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -3962,9 +3942,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; @@ -4039,9 +4017,7 @@ class MyComponent extends Component { template. Class top level template. Class top level template. - + } " `; From 649c078ec1dc0b0e945cf339fa761c94d7ff9b5c Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 18:58:06 -0800 Subject: [PATCH 13/32] Remove unnecessary try/catch --- src/print/index.ts | 27 ++++++++++----------------- src/print/template.ts | 7 ++----- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/print/index.ts b/src/print/index.ts index 090b7548..25fd086b 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -157,23 +157,16 @@ export const printer: Printer = { return async (textToDoc) => { try { if (node && isGlimmerTemplate(node)) { - let content = null; - let raw = false; - try { - content = await printTemplateContent( - node.extra.template, - textToDoc, - embedOptions as Options, - ); - } catch { - content = node.extra.template; - raw = true; - } - - const printed = printTemplateTag(content, { - useHardline: node.extra.isDefaultTemplate, - raw, - }); + const content = await printTemplateContent( + node.extra.template, + textToDoc, + embedOptions as Options, + ); + + const printed = printTemplateTag( + content, + node.extra.isDefaultTemplate, + ); saveCurrentPrintOnSiblingNode(path, printed); return printed; } diff --git a/src/print/template.ts b/src/print/template.ts index ec28f60e..013908a5 100644 --- a/src/print/template.ts +++ b/src/print/template.ts @@ -44,12 +44,9 @@ export async function printTemplateContent( */ export function printTemplateTag( content: doc.builders.Doc, - options: { - useHardline: boolean; - raw: boolean; - }, + useHardline: boolean, ): doc.builders.Doc { - const line = options.raw ? '' : options.useHardline ? hardline : softline; + const line = useHardline ? hardline : softline; const doc = [ TEMPLATE_TAG_OPEN, indent([line, group(content)]), From 2f2defedf8f09f7466fff9d8b78e8af43c88df5a Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 19:10:47 -0800 Subject: [PATCH 14/32] Clean up fixPreviousPrint hack --- src/print/index.ts | 15 ++++++++++----- src/print/template.ts | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/print/index.ts b/src/print/index.ts index 25fd086b..f8af152e 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -55,7 +55,7 @@ function flattenDoc(doc: doc.builders.Doc): string[] { */ function saveCurrentPrintOnSiblingNode( path: AstPath, - printed: doc.builders.Doc, + printed: doc.builders.Doc[], ): void { const { index, siblings } = path; if (index !== null) { @@ -69,7 +69,9 @@ function saveCurrentPrintOnSiblingNode( } } +/** HACK to fix ASI semi-colons. */ function fixPreviousPrint( + previousTemplatePrinted: doc.builders.Doc[], path: AstPath, options: Options, print: (path: AstPath) => doc.builders.Doc, @@ -82,9 +84,6 @@ function fixPreviousPrint( args, ); const flat = flattenDoc(printedSemiFalse); - const previousTemplatePrinted = path.node?.extra?.[ - 'prevTemplatePrinted' - ] as string[]; const previousFlat = flattenDoc(previousTemplatePrinted); if (flat[0]?.startsWith(';') && previousFlat.at(-1) !== ';') { previousTemplatePrinted.push(';'); @@ -136,7 +135,13 @@ export const printer: Printer = { } if (options.semi && node?.extra?.['prevTemplatePrinted']) { - fixPreviousPrint(path, options, print, args); + fixPreviousPrint( + node.extra['prevTemplatePrinted'] as doc.builders.Doc[], + path, + options, + print, + args, + ); } return hasPrettierIgnore diff --git a/src/print/template.ts b/src/print/template.ts index 013908a5..ec7d7e7e 100644 --- a/src/print/template.ts +++ b/src/print/template.ts @@ -45,7 +45,7 @@ export async function printTemplateContent( export function printTemplateTag( content: doc.builders.Doc, useHardline: boolean, -): doc.builders.Doc { +): doc.builders.Doc[] { const line = useHardline ? hardline : softline; const doc = [ TEMPLATE_TAG_OPEN, From ff7cadc49acfc3d8f8b41f104bb8123e5d46c1df Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 19:13:32 -0800 Subject: [PATCH 15/32] Clean up printer some more --- src/print/index.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/print/index.ts b/src/print/index.ts index f8af152e..279598f3 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -160,8 +160,8 @@ export const printer: Printer = { } return async (textToDoc) => { - try { - if (node && isGlimmerTemplate(node)) { + if (node && isGlimmerTemplate(node)) { + try { const content = await printTemplateContent( node.extra.template, textToDoc, @@ -174,11 +174,11 @@ export const printer: Printer = { ); saveCurrentPrintOnSiblingNode(path, printed); return printed; + } catch { + const printed = [printRawText(path, embedOptions as Options)]; + saveCurrentPrintOnSiblingNode(path, printed); + return printed; } - } catch { - const printed = [printRawText(path, embedOptions as Options)]; - saveCurrentPrintOnSiblingNode(path, printed); - return printed; } // Nothing to embed, so move on to the regular printer. @@ -194,10 +194,7 @@ export const printer: Printer = { hasPrettierIgnore: undefined, }; -/** - * Remove the semicolons and empty strings that Prettier added so we can manage - * them. - */ +/** Remove the empty strings that Prettier added so we can manage them. */ function trimPrinted(printed: doc.builders.Doc[]): void { while (docMatchesString(printed[0], '')) { printed.shift(); From 3bb2b675a2be81bbe43512eef4398f6f82050306 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 19:17:31 -0800 Subject: [PATCH 16/32] More printer cleanup --- src/print/ambiguity.ts | 61 ++++++++++++++++++++++++++++++++ src/print/index.ts | 79 ++---------------------------------------- src/types/glimmer.ts | 26 ++++++++++++++ 3 files changed, 89 insertions(+), 77 deletions(-) create mode 100644 src/print/ambiguity.ts diff --git a/src/print/ambiguity.ts b/src/print/ambiguity.ts new file mode 100644 index 00000000..5f5d71f4 --- /dev/null +++ b/src/print/ambiguity.ts @@ -0,0 +1,61 @@ +import type { Node } from '@babel/types'; +import type { AstPath, doc, Printer } from 'prettier'; +import { printers as estreePrinters } from 'prettier/plugins/estree.js'; + +import type { Options } from '../options.js'; + +const estreePrinter = estreePrinters['estree'] as Printer; + +/** NOTE: This is highly specialized for use in `fixPreviousPrint` */ +function flattenDoc(doc: doc.builders.Doc): string[] { + if (Array.isArray(doc)) { + return doc.flatMap(flattenDoc); + } else if (typeof doc === 'string') { + return [doc]; + } else if ('contents' in doc) { + return flattenDoc(doc.contents); + } else { + return []; + } +} + +/** + * Search next non EmptyStatement node and set current print, so we can fix it + * later if its ambiguous + */ +export function saveCurrentPrintOnSiblingNode( + path: AstPath, + printed: doc.builders.Doc[], +): void { + const { index, siblings } = path; + if (index !== null) { + const nextNode = siblings + ?.slice(index + 1) + .find((n) => n?.type !== 'EmptyStatement'); + if (nextNode) { + nextNode.extra = nextNode.extra || {}; + nextNode.extra['prevTemplatePrinted'] = printed; + } + } +} + +/** HACK to fix ASI semi-colons. */ +export function fixPreviousPrint( + previousTemplatePrinted: doc.builders.Doc[], + path: AstPath, + options: Options, + print: (path: AstPath) => doc.builders.Doc, + args: unknown, +): void { + const printedSemiFalse = estreePrinter.print( + path, + { ...options, semi: false }, + print, + args, + ); + const flat = flattenDoc(printedSemiFalse); + const previousFlat = flattenDoc(previousTemplatePrinted); + if (flat[0]?.startsWith(';') && previousFlat.at(-1) !== ';') { + previousTemplatePrinted.push(';'); + } +} diff --git a/src/print/index.ts b/src/print/index.ts index 279598f3..f2f3137a 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -8,88 +8,13 @@ import type { import { printers as estreePrinters } from 'prettier/plugins/estree.js'; import type { Options } from '../options.js'; -import type { GlimmerTemplate } from '../types/glimmer'; -import { isGlimmerTemplate } from '../types/glimmer'; +import { getGlimmerTemplate, isGlimmerTemplate } from '../types/glimmer'; import { assert } from '../utils'; +import { fixPreviousPrint, saveCurrentPrintOnSiblingNode } from './ambiguity'; import { printTemplateContent, printTemplateTag } from './template'; const estreePrinter = estreePrinters['estree'] as Printer; -function getGlimmerTemplate(node: Node | undefined): GlimmerTemplate | null { - if (!node) return null; - if (isGlimmerTemplate(node)) { - return node; - } - if ( - node.type === 'ExportDefaultDeclaration' && - isGlimmerTemplate(node.declaration) - ) { - return node.declaration; - } - if ( - node.type === 'ExportDefaultDeclaration' && - node.declaration.type === 'TSAsExpression' && - isGlimmerTemplate(node.declaration.expression) - ) { - return node.declaration.expression; - } - return null; -} - -/** NOTE: This is highly specialized for use in `fixPreviousPrint` */ -function flattenDoc(doc: doc.builders.Doc): string[] { - if (Array.isArray(doc)) { - return doc.flatMap(flattenDoc); - } else if (typeof doc === 'string') { - return [doc]; - } else if ('contents' in doc) { - return flattenDoc(doc.contents); - } else { - return []; - } -} - -/** - * Search next non EmptyStatement node and set current print, so we can fix it - * later if its ambiguous - */ -function saveCurrentPrintOnSiblingNode( - path: AstPath, - printed: doc.builders.Doc[], -): void { - const { index, siblings } = path; - if (index !== null) { - const nextNode = siblings - ?.slice(index + 1) - .find((n) => n?.type !== 'EmptyStatement'); - if (nextNode) { - nextNode.extra = nextNode.extra || {}; - nextNode.extra['prevTemplatePrinted'] = printed; - } - } -} - -/** HACK to fix ASI semi-colons. */ -function fixPreviousPrint( - previousTemplatePrinted: doc.builders.Doc[], - path: AstPath, - options: Options, - print: (path: AstPath) => doc.builders.Doc, - args: unknown, -): void { - const printedSemiFalse = estreePrinter.print( - path, - { ...options, semi: false }, - print, - args, - ); - const flat = flattenDoc(printedSemiFalse); - const previousFlat = flattenDoc(previousTemplatePrinted); - if (flat[0]?.startsWith(';') && previousFlat.at(-1) !== ';') { - previousTemplatePrinted.push(';'); - } -} - export const printer: Printer = { ...estreePrinter, diff --git a/src/types/glimmer.ts b/src/types/glimmer.ts index a28b491c..bd563a6e 100644 --- a/src/types/glimmer.ts +++ b/src/types/glimmer.ts @@ -81,3 +81,29 @@ export function isDefaultTemplate(path: NodePath): boolean { path.parentPath?.parentPath?.parent.type === 'Program') ); } + +/** + * + */ +export function getGlimmerTemplate( + node: Node | undefined, +): GlimmerTemplate | null { + if (!node) return null; + if (isGlimmerTemplate(node)) { + return node; + } + if ( + node.type === 'ExportDefaultDeclaration' && + isGlimmerTemplate(node.declaration) + ) { + return node.declaration; + } + if ( + node.type === 'ExportDefaultDeclaration' && + node.declaration.type === 'TSAsExpression' && + isGlimmerTemplate(node.declaration.expression) + ) { + return node.declaration.expression; + } + return null; +} From 24d83095fd852dcb7c1e3aebebcbd1b449a4087f Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Sat, 2 Dec 2023 19:27:04 -0800 Subject: [PATCH 17/32] Snapshot parse errors in tests --- tests/helpers/ambiguous.ts | 1 + .../arrow-parens-avoid.test.ts.snap | 408 ++++++ .../__snapshots__/index.test.ts.snap | 1139 +++++++++++++++-- .../__snapshots__/semi-false.test.ts.snap | 1139 +++++++++++++++-- 4 files changed, 2479 insertions(+), 208 deletions(-) diff --git a/tests/helpers/ambiguous.ts b/tests/helpers/ambiguous.ts index 1a05cb97..0219da0a 100644 --- a/tests/helpers/ambiguous.ts +++ b/tests/helpers/ambiguous.ts @@ -107,5 +107,6 @@ async function behavesLikeFormattedAmbiguousCase( throw error; } expect(isSyntaxError, 'Expected SyntaxError').toBeTruthy(); + expect(`\n${error}\n`).toMatchSnapshot(); } } diff --git a/tests/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap b/tests/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap index 3b9549ac..a37971c2 100644 --- a/tests/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap +++ b/tests/unit-tests/ambiguous/__snapshots__/arrow-parens-avoid.test.ts.snap @@ -1,5 +1,17 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`ambiguous > config > arrowParens: "avoid" > (oh, no) => {} > with semi, with newline > it formats ../cases/gjs/component-class.gjs 1`] = ` +" +Error: Parse Error at :11:3: 11:4 +" +`; + +exports[`ambiguous > config > arrowParens: "avoid" > (oh, no) => {} > with semi, with newline > it formats ../cases/gjs/component-class-with-content-before-template.gjs 1`] = ` +" +Error: Parse Error at :13:3: 13:4 +" +`; + exports[`ambiguous > config > arrowParens: "avoid" > (oh, no) => {} > with semi, with newline > it formats ../cases/gjs/default-export.gjs 1`] = ` "