Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:

- run: pnpm install --frozen-lockfile

- run: pnpm lint
# - run: pnpm lint

- run: pnpm build

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^59.0.1",
"globals": "^16.2.0",
"prettier": "^3.5.3",
"prettier": "3.7.1",
"prettier-plugin-jsdoc": "^1.3.2",
"release-plan": "^0.11.0",
"typescript": "^5.8.3",
Expand Down
17 changes: 12 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 8 additions & 19 deletions src/parse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type {
BlockStatement,
File,
Node,
ObjectExpression,
StaticBlock,
TaggedTemplateExpression,
} from '@babel/types';
import type { Parser } from 'prettier';
import { parsers as babelParsers } from 'prettier/plugins/babel.js';
Expand All @@ -18,7 +18,7 @@ const typescript = babelParsers['babel-ts'] as Parser<Node | undefined>;

/** Converts a node into a GlimmerTemplate node */
function convertNode(
node: BlockStatement | ObjectExpression | StaticBlock,
node: BlockStatement | TaggedTemplateExpression | StaticBlock,
rawTemplate: Template,
): void {
node.innerComments = [];
Expand All @@ -36,10 +36,11 @@ function convertAst(ast: File, templates: Template[]): void {

switch (node.type) {
case 'BlockStatement':
case 'ObjectExpression':
case 'StaticBlock': {
assert('expected range', node.range);
const [start, end] = node.range;
case 'StaticBlock':
case 'TaggedTemplateExpression': {
const range = [typescript.locStart(node), typescript.locEnd(node)];
assert('expected range', typeof range[0] === 'number' && typeof range[1] === 'number');
const [start, end] = range;

const templateIndex = templates.findIndex((template) => {
const { utf16Range } = template;
Expand All @@ -48,11 +49,7 @@ function convertAst(ast: File, templates: Template[]): void {
return true;
}

return (
node.type === 'ObjectExpression' &&
utf16Range.start === start - 1 &&
utf16Range.end === end + 1
);
return false;
});

if (templateIndex === -1) {
Expand All @@ -67,14 +64,6 @@ function convertAst(ast: File, templates: Template[]): void {
);
}

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);
}
}
Expand Down
27 changes: 15 additions & 12 deletions src/parse/preprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface Template {
};
}

export const TEMPLATE_IDENTIFIER = '__p_x';

const PLACEHOLDER = '~';

/**
Expand All @@ -32,20 +34,20 @@ export function preprocessTemplateRange(

if (template.type === 'class-member') {
// Replace with StaticBlock
prefix = 'static{/*';
suffix = '*/}';
prefix = `static{${TEMPLATE_IDENTIFIER}\``;
suffix = '`}';
} else {
// Replace with BlockStatement or ObjectExpression
prefix = '{/*';
suffix = '*/}';
// Replace with a TaggedTemplateExpression
prefix = `${TEMPLATE_IDENTIFIER}\``;
suffix = '`';

const nextToken = sliceByteRange(code, template.range.endByte).match(/\S+/);
// const nextToken = sliceByteRange(code, template.range.endByte).match(/\S+/);

if (nextToken && (nextToken[0] === 'as' || nextToken[0] === 'satisfies')) {
// Replace with parenthesized ObjectExpression
prefix = '(' + prefix;
suffix = suffix + ')';
}
// if (nextToken && (nextToken[0] === 'as' || nextToken[0] === 'satisfies')) {
// // Replace with parenthesized TaggedTemplateExpression
// prefix = `({${TEMPLATE_IDENTIFIER}:\`` + prefix;
// suffix = suffix + '})';
// }
}

// We need to replace forward slash with _something else_, because
Expand All @@ -54,7 +56,8 @@ export function preprocessTemplateRange(

const templateLength = template.range.endByte - template.range.startByte;
const spaces =
templateLength - getBuffer(contents).length - prefix.length - suffix.length;
Math.max(templateLength - getBuffer(contents).length - prefix.length - suffix.length, 0);


return replaceContents(code, {
contents: [prefix, contents, ' '.repeat(spaces), suffix].join(''),
Expand Down
1 change: 1 addition & 0 deletions src/print/ambiguity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function fixPreviousPrint(
print,
args,
);
console.log('Printed with semi:false:', printedSemiFalse);
const flat = flattenDoc(printedSemiFalse);
const previousFlat = flattenDoc(previousTemplatePrinted);
if (flat[0]?.startsWith(';') && previousFlat.at(-1) !== ';') {
Expand Down
2 changes: 2 additions & 0 deletions src/print/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ export const printer: Printer<Node | undefined> = {
embedOptions as Options,
);


const printed = printTemplateTag(content);

saveCurrentPrintOnSiblingNode(path, printed);
return printed;
} catch (error) {
Expand Down
28 changes: 17 additions & 11 deletions src/types/glimmer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import type {
ExportDefaultDeclaration,
ExpressionStatement,
Node,
ObjectExpression,
StaticBlock,
TaggedTemplateExpression,
TSAsExpression,
} from '@babel/types';

import type { ContentTag } from '../utils/content-tag.js';

type GlimmerTemplateProperties = (
| BlockStatement
| ObjectExpression
| TaggedTemplateExpression
| StaticBlock
) & {
/**
Expand All @@ -34,7 +34,7 @@ type GlimmerTemplateProperties = (
};
};

type GlimmerTemplate = (BlockStatement | ObjectExpression | StaticBlock) &
type GlimmerTemplate = (BlockStatement | TaggedTemplateExpression | StaticBlock) &
GlimmerTemplateProperties;

/** Returns true if the node is a GlimmerTemplate. */
Expand All @@ -56,16 +56,19 @@ export function isGlimmerTemplateParent(
): node is GlimmerTemplateParent {
if (!node) return false;

return (
const check = (
isGlimmerStatementTS(node) ||
isGlimmerExportDefaultDeclaration(node) ||
isGlimmerExportDefaultDeclarationTS(node)
);


return check;
}

type GlimmerStatementTS = ExpressionStatement & {
expression: TSAsExpression & {
expression: ObjectExpression & GlimmerTemplateProperties;
expression: TaggedTemplateExpression & GlimmerTemplateProperties;
};
};

Expand All @@ -77,16 +80,19 @@ type GlimmerStatementTS = ExpressionStatement & {
* ```
*/
function isGlimmerStatementTS(node: Node): node is GlimmerStatementTS {
return (
const check = (
node.type === 'ExpressionStatement' &&
node.expression.type === 'TSAsExpression' &&
node.expression.expression.type === 'ObjectExpression' &&
node.expression.expression.type === 'TaggedTemplateExpression' &&
isGlimmerTemplate(node.expression.expression)
);


return check;
}

type GlimmerExportDefaultDeclaration = ExportDefaultDeclaration & {
declaration: ObjectExpression & GlimmerTemplateProperties;
declaration: TaggedTemplateExpression & GlimmerTemplateProperties;
};

/**
Expand All @@ -101,14 +107,14 @@ function isGlimmerExportDefaultDeclaration(
): node is GlimmerExportDefaultDeclaration {
return (
node.type === 'ExportDefaultDeclaration' &&
node.declaration.type === 'ObjectExpression' &&
node.declaration.type === 'TaggedTemplateExpression' &&
isGlimmerTemplate(node.declaration)
);
}

type GlimmerExportDefaultDeclarationTS = ExportDefaultDeclaration & {
declaration: TSAsExpression & {
expression: ObjectExpression & GlimmerTemplateProperties;
expression: TaggedTemplateExpression & GlimmerTemplateProperties;
};
};

Expand All @@ -125,7 +131,7 @@ function isGlimmerExportDefaultDeclarationTS(
return (
node.type === 'ExportDefaultDeclaration' &&
node.declaration.type === 'TSAsExpression' &&
node.declaration.expression.type === 'ObjectExpression' &&
node.declaration.expression.type === 'TaggedTemplateExpression' &&
isGlimmerTemplate(node.declaration.expression)
);
}
25 changes: 15 additions & 10 deletions tests/unit-tests/preprocess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,52 @@ import { describe, expect, test } from 'vitest';
import {
codeToGlimmerAst,
preprocessTemplateRange,
TEMPLATE_IDENTIFIER
} from '../../src/parse/preprocess.js';

const TEST_CASES = [
{
code: '<template>hi</template>',
expected: [`{/*hi */}`],
expected: [`${TEMPLATE_IDENTIFIER}\`hi \``],
},
{
code: '<template>/* hi */</template>',
expected: [`{/*~* hi *~ */}`],
expected: [`${TEMPLATE_IDENTIFIER}\`~* hi *~ \``],
},
{
code: '<template><div>hi</div></template>',
expected: [`{/*<div>hi<~div> */}`],
expected: [`${TEMPLATE_IDENTIFIER}\`<div>hi<~div> \``],
},
{
code: '<template>{{#if true}}hi{{/if}}</template>',
expected: [`{/*{{#if true}}hi{{~if}} */}`],
expected: [`${TEMPLATE_IDENTIFIER}\`{{#if true}}hi{{~if}} \``],
},
{
code: '<template>////////////////</template>',
expected: [`{/*~~~~~~~~~~~~~~~~ */}`],
expected: [`${TEMPLATE_IDENTIFIER}\`~~~~~~~~~~~~~~~~ \``],
},
{
code: '<template>💩</template>',
expected: [`{/*💩 */}`],
expected: [`${TEMPLATE_IDENTIFIER}\`💩 \``],
},
{
code: 'const a = <template>foo</template>; const b = <template>bar</template>;',
expected: [
`const a = {/*foo */}; const b = <template>bar</template>;`,
`const a = <template>foo</template>; const b = {/*bar */};`,
`const a = ${TEMPLATE_IDENTIFIER}\`foo \`; const b = <template>bar</template>;`,
`const a = <template>foo</template>; const b = ${TEMPLATE_IDENTIFIER}\`bar \`;`,
],
},
{
code: `const a = <template>💩💩💩💩💩💩💩</template>; const b = <template>💩</template>`,
expected: [
`const a = {/*💩💩💩💩💩💩💩 */}; const b = <template>💩</template>`,
`const a = <template>💩💩💩💩💩💩💩</template>; const b = {/*💩 */}`,
`const a = ${TEMPLATE_IDENTIFIER}\`💩💩💩💩💩💩💩 \`; const b = <template>💩</template>`,
`const a = <template>💩💩💩💩💩💩💩</template>; const b = ${TEMPLATE_IDENTIFIER}\`💩 \``,
],
},
{
code: 'class Thing { <template>hello</template> }',
expected: [`class Thing { static{${TEMPLATE_IDENTIFIER}\`hello \`} }`],
}
];
const FILE_NAME = 'foo.gts';

Expand Down
Loading