Skip to content

Commit 3b2b3a0

Browse files
authored
Merge pull request #35 from gitKrystan/hardline-defaults
Print template tags on separate lines for "default" templates
2 parents 8655717 + 9982684 commit 3b2b3a0

11 files changed

Lines changed: 894 additions & 289 deletions

File tree

README.md

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,63 @@ A [Prettier](https://prettier.io/) plugin for formatting [Ember template tags](h
4242

4343
## Opinions
4444

45-
This plugin works by weaving together Prettier's standard [babel-ts](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBLWcBOAzAhmOAAgFkBPASQx3yOAB0pDDsIIAKASiUIGcZN0AcwA0DJgCNcmbgEFMmXKQA8UAK4BbcVgB8oqAF8GDOAA8ADhEwxCucXwVhrYADa4ePQgDFWhVOrPOcOoIMB5klPDUBIT0jMysnDFiTCmpTAD06YQAKgDyACK53JhwMKqYjLiVjqq4zoQAbnWqRAAWWHDJaYQlZRWEAOTtzs4QA10phnGCpYSSmImxqb3ljADaTACMehNMAEzChADMeqkAuslTUwwwpGZEAEpwAI4tfNl3RAC8gwDiAKLZAaEAA+gwAEv8ZPlgWCBgAFXIAZSBoMG8IAqqi4bl4dlyLkAHJI2GDADCRMJ-zJ2MG+X+ABlAf9SQNsg8ZGSWSBhCAIGYYKhoDxkKApJgIAB3eFSBAilB1SWKEW88QOADWpSRuGCDPQcGQeGcPDgqo1WrM+CEyH4LV5QS0ABNHXBHQyqoJajNvJh1LgYIKoIJkCBcKoYBAeSBWjB1M4AOqtVDwHiWghIuXJ1ANZOkENgdxR9AmqzwhSCP2Guom3kAKx4JiRQkCAEVVBB4FXjaaQJbMCWQ5ItM4ALShKNmASweOoR0wVrIAAcAAZeZOICb4wozCHJ3ASw0DbzXh24GX+fLQzwR1A4K7XVGSq9UCUy7gK7guzWQCb1KgbZgdo-s2cBtqeX49jAtgznOC5IAcID8LgqDOEIZIQOolYoPuACsUaqCa2S2PKRrfg0LSUC6sBImAAgCjIUCOkityBBB+j6EAA) and [glimmer](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEACVAeAJgSwG7qFgA2AhgM7kC8AOiAjAE4CedhqAfDVOhgBYBGDsGDoYOGMTjsAvjIwB6QVx6ZcBdCQrU6AIwhZWIFe1OERhfYbOE53Reo6YFjkABoQEAA7jo5ZKCkjIwQAO4ACkEI-iikxKGkzP4euoykYADWcDAAyqQAtnAAMjhQcMgAZnHkcClpmdk5XumlAObITACutfT5unBYWANFpFCtnaStcABiEIz5pDDiY8ggpJ0wEO4gfDD5xADqfBJw5M1gcDnREvgSzKtglNulNYww4WmtC5XVPQBW5AAHjk2lIAIqdCDwH7EGoeZqMV6rVrEHD5QqMbZeRilGAHHBYGB8ZAADgADPCQjUDmkvKtsac4Iw8OUPABHSHwD7eGJrcgAWjKAwG20YcA5ODFH0m3yQVVhPRq+RwHUY3Q85FBcAhUPKct+HhgpF0+MJxKQACZDWkcKixgBhCDo0irU4AVm2nRqABVjTF5XCQHhugBJKBDWA5MA4nwAQXDORgzCkMJqciAA) parsers, so it doesn't have many opinions of its own.
45+
This plugin works by weaving together Prettier's standard [babel-ts](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBLWcBOAzAhmOAAgFkBPASQx3yOAB0pDDsIIAKASiUIGcZN0AcwA0DJgCNcmbgEFMmXKQA8UAK4BbcVgB8oqAF8GDOAA8ADhEwxCucXwVhrYADa4ePQgDFWhVOrPOcOoIMB5klPDUBIT0jMysnDFiTCmpTAD06YQAKgDyACK53JhwMKqYjLiVjqq4zoQAbnWqRAAWWHDJaYQlZRWEAOTtzs4QA10phnGCpYSSmImxqb3ljADaTACMehNMAEzChADMeqkAuslTUwwwpGZEAEpwAI4tfNl3RAC8gwDiAKLZAaEAA+gwAEv8ZPlgWCBgAFXIAZSBoMG8IAqqi4bl4dlyLkAHJI2GDADCRMJ-zJ2MG+X+ABlAf9SQNsg8ZGSWSBhCAIGYYKhoDxkKApJgIAB3eFSBAilB1SWKEW88QOADWpSRuGCDPQcGQeGcPDgqo1WrM+CEyH4LV5QS0ABNHXBHQyqoJajNvJh1LgYIKoIJkCBcKoYBAeSBWjB1M4AOqtVDwHiWghIuXJ1ANZOkENgdxR9AmqzwhSCP2Guom3kAKx4JiRQkCAEVVBB4FXjaaQJbMCWQ5ItM4ALShKNmASweOoR0wVrIAAcAAZeZOICb4wozCHJ3ASw0DbzXh24GX+fLQzwR1A4K7XVGSq9UCUy7gK7guzWQCb1KgbZgdo-s2cBtqeX49jAtgznOC5IAcID8LgqDOEIZIQOolYoPuACsUaqCa2S2PKRrfg0LSUC6sBImAAgCjIUCOkityBBB+j6EAA) and [glimmer](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEACVAeAJgSwG7qFgA2AhgM7kC8AOiAjAE4CedhqAfDVOhgBYBGDsGDoYOGMTjsAvjIwB6QVx6ZcBdCQrU6AIwhZWIFe1OERhfYbOE53Reo6YFjkABoQEAA7jo5ZKCkjIwQAO4ACkEI-iikxKGkzP4euoykYADWcDAAyqQAtnAAMjhQcMgAZnHkcClpmdk5XumlAObITACutfT5unBYWANFpFCtnaStcABiEIz5pDDiY8ggpJ0wEO4gfDD5xADqfBJw5M1gcDnREvgSzKtglNulNYww4WmtC5XVPQBW5AAHjk2lIAIqdCDwH7EGoeZqMV6rVrEHD5QqMbZeRilGAHHBYGB8ZAADgADPCQjUDmkvKtsac4Iw8OUPABHSHwD7eGJrcgAWjKAwG20YcA5ODFH0m3yQVVhPRq+RwHUY3Q85FBcAhUPKct+HhgpF0+MJxKQACZDWkcKixgBhCDo0irU4AVm2nRqABVjTF5XCQHhugBJKBDWA5MA4nwAQXDORgzCkMJqciAA) parsers, so it doesn't have many opinions of its own. With that said, I did have to make some opinionated decisions regarding how templates are printed within the JavaScript. In general, my strategy has been to ask "What would a function do?" since functions can be used in the same positions as the `<template>` tag.
4646

47-
With that said, I did have to make some decisions about when and where to include or omit semicolons. You can read more about and comment on my decision process [on this RFC](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1).
47+
### Semicolons
48+
49+
Semicolons will be included or omitted as follows:
50+
51+
```js
52+
export default class MyComponent extends Component {
53+
<template>Hello</template> // omit
54+
}
55+
56+
<template>Hello</template> // omit
57+
58+
export default <template>Hello</template> // omit
59+
60+
export const MyComponent = <template>Hello</template>; // include by default, omit in no-semi mode
61+
```
62+
63+
You can read more about and comment on my semicolon decision process [on this RFC](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1).
64+
65+
### New Lines
66+
67+
Template tags will always be printed on their own line for templates that are default exports or top-level class templates. Templates used as expressions will be allowed to collapse onto a single line if they fit:
68+
69+
```js
70+
import Component from '@glimmer/component';
71+
72+
const what = <template>Used as an expression</template>;
73+
74+
export const who = <template>Used as an expression</template>;
75+
76+
<template>
77+
The default export
78+
</template>
79+
80+
class MyComponent extends Component {
81+
<template>
82+
Top-level class template
83+
</template>
84+
}
85+
```
86+
87+
### `export default`
88+
89+
The following `<template>` invocations both desugar to default exports:
90+
91+
```js
92+
// template-only-component.js
93+
94+
<template>Hello</template>;
95+
96+
// or
97+
98+
export default <template>Hello</template>;
99+
```
100+
101+
By default, this plugin will remove `export default` to print the more concise "sugared" default export template. If you would prefer to always include `export default`, you can enable the `templateExportDefault` option described below.
48102

49103
## Configuration
50104

src/parse.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import type {
2323
GlimmerExpressionExtra,
2424
GlimmerTemplateExtra,
2525
} from './types/glimmer';
26+
import {
27+
getGlimmerExpression,
28+
isGlimmerClassProperty,
29+
isGlimmerExportDefaultDeclaration,
30+
isGlimmerExportDefaultDeclarationTS,
31+
} from './types/glimmer';
2632
import type {
2733
RawGlimmerArrayExpression,
2834
RawGlimmerClassProperty,
@@ -84,6 +90,7 @@ export const parser: Parser<BaseNode | undefined> = {
8490
const ast = typescript.parse(text, parsers, options);
8591
traverse(ast as Node, {
8692
enter: makeEnter(options),
93+
exit: makeExit(),
8794
});
8895
assert('expected ast', ast);
8996
return ast;
@@ -131,12 +138,24 @@ function makeEnter(options: Options): (path: NodePath) => void {
131138
};
132139
}
133140

141+
function makeExit(): (path: NodePath) => void {
142+
return ({ node }: NodePath) => {
143+
if (
144+
isGlimmerExportDefaultDeclaration(node) ||
145+
isGlimmerExportDefaultDeclarationTS(node) ||
146+
isGlimmerClassProperty(node)
147+
) {
148+
getGlimmerExpression(node).extra.isDefaultTemplate = true;
149+
}
150+
};
151+
}
152+
134153
function tagGlimmerExpression(
135154
node: RawGlimmerArrayExpression | RawGlimmerClassProperty,
136155
forceSemi: boolean
137156
): void {
138157
const extra: GlimmerExpressionExtra = {
139-
hasGlimmerExpression: true,
158+
isGlimmerTemplate: true,
140159
forceSemi,
141160
};
142161
node.extra =

src/print/index.ts

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { AstPath, Plugin, Printer } from 'prettier';
2-
import { doc } from 'prettier';
1+
import type { AstPath, doc, Plugin, Printer } from 'prettier';
32

43
import {
54
TEMPLATE_TAG_CLOSE,
@@ -19,15 +18,15 @@ import {
1918
isGlimmerTemplateLiteral,
2019
} from '../types/glimmer';
2120
import { assert, assertExists } from '../utils/index';
22-
import { printTemplateTag } from './template';
21+
import {
22+
printTemplateContent,
23+
printTemplateLiteral,
24+
printTemplateTag,
25+
} from './template';
2326

2427
// @ts-expect-error FIXME: HACK because estree printer isn't exported. See below.
2528
export const printer: Printer<BaseNode | undefined> = {};
2629

27-
const {
28-
builders: { group, indent, softline },
29-
} = doc;
30-
3130
/**
3231
* FIXME: HACK because estree printer isn't exported.
3332
*
@@ -130,32 +129,23 @@ export function definePrinter(options: Options): void {
130129
if (hasPrettierIgnore) {
131130
return printRawText(path, embedOptions);
132131
} else if (wasPreprocessed && isGlimmerTemplateLiteral(node)) {
133-
const contents = printTemplateTag(node, textToDoc, embedOptions);
134-
return group(['`', contents, '`']);
132+
return printTemplateLiteral(
133+
printTemplateContent(node, textToDoc, embedOptions)
134+
);
135135
} else if (!wasPreprocessed && isGlimmerClassProperty(node)) {
136-
const contents = printTemplateTag(
137-
node.key.arguments[0],
138-
textToDoc,
139-
embedOptions
136+
return printTemplateTag(
137+
printTemplateContent(node.key.arguments[0], textToDoc, embedOptions),
138+
node.extra.isDefaultTemplate ?? false
140139
);
141-
return group([
142-
TEMPLATE_TAG_OPEN,
143-
indent([softline, group(contents)]),
144-
softline,
145-
TEMPLATE_TAG_CLOSE,
146-
]);
147140
} else if (!wasPreprocessed && isGlimmerArrayExpression(node)) {
148-
const contents = printTemplateTag(
149-
node.elements[0].arguments[0],
150-
textToDoc,
151-
embedOptions
141+
return printTemplateTag(
142+
printTemplateContent(
143+
node.elements[0].arguments[0],
144+
textToDoc,
145+
embedOptions
146+
),
147+
node.extra.isDefaultTemplate ?? false
152148
);
153-
return group([
154-
TEMPLATE_TAG_OPEN,
155-
indent([softline, group(contents)]),
156-
softline,
157-
TEMPLATE_TAG_CLOSE,
158-
]);
159149
} else {
160150
// Nothing to embed, so move on to the regular printer.
161151
return null;

src/print/template.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import type { TemplateLiteral } from '@babel/types';
2-
import type { doc, ParserOptions } from 'prettier';
2+
import type { ParserOptions } from 'prettier';
3+
import { doc } from 'prettier';
4+
import { TEMPLATE_TAG_CLOSE, TEMPLATE_TAG_OPEN } from '../config';
35

46
import type { Options } from '../options';
57
import { getTemplateSingleQuote } from '../options';
68
import type { BaseNode } from '../types/ast';
79

10+
const {
11+
builders: { group, hardline, indent, softline },
12+
} = doc;
13+
814
/**
9-
* Returns a Prettier `Doc` for the given `TemplateLiteral` that is formatted
15+
* Returns a Prettier `Doc` for the given `TemplateLiteral` contents formatted
1016
* using Prettier's built-in glimmer parser.
17+
*
18+
* NOTE: The contents are not surrounded with "`"
1119
*/
12-
export function printTemplateTag(
20+
export function printTemplateContent(
1321
node: TemplateLiteral,
1422
textToDoc: (
1523
text: string,
@@ -27,3 +35,32 @@ export function printTemplateTag(
2735
singleQuote: getTemplateSingleQuote(options),
2836
});
2937
}
38+
39+
/**
40+
* Prints the given template content as a template tag.
41+
*
42+
* If `useHardline` is `true`, will use Prettier's hardline builder to force
43+
* each tag to print on a new line.
44+
*
45+
* If `useHardline` is `false`, will use Prettier's softline builder to allow
46+
* the tags to print on the same line if they fit.
47+
*/
48+
export function printTemplateTag(
49+
content: doc.builders.Doc,
50+
useHardline: boolean
51+
): doc.builders.Doc {
52+
const line = useHardline ? hardline : softline;
53+
return group([
54+
TEMPLATE_TAG_OPEN,
55+
indent([line, group(content)]),
56+
line,
57+
TEMPLATE_TAG_CLOSE,
58+
]);
59+
}
60+
61+
/** Prints the given template content as a template literal. */
62+
export function printTemplateLiteral(
63+
content: doc.builders.Doc
64+
): doc.builders.Doc {
65+
return group(['`', content, '`']);
66+
}

src/types/glimmer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export function isGlimmerTemplateLiteral(
3838

3939
export interface GlimmerExpressionExtra {
4040
forceSemi: boolean;
41-
hasGlimmerExpression: true;
41+
isGlimmerTemplate: true;
42+
isDefaultTemplate?: boolean;
4243
[key: string]: unknown;
4344
}
4445

@@ -48,7 +49,7 @@ export type GlimmerExpression = GlimmerArrayExpression | GlimmerClassProperty;
4849
export function isGlimmerExpression(
4950
node: BaseNode | null | undefined
5051
): node is GlimmerExpression {
51-
return node?.extra?.['hasGlimmerExpression'] === true;
52+
return node?.extra?.['isGlimmerTemplate'] === true;
5253
}
5354

5455
export interface GlimmerArrayExpression extends RawGlimmerArrayExpression {

tests/unit-tests/__snapshots__/format.test.ts.snap

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,9 @@ exports[`format > config > default > it formats ../cases/gjs/prettier-ignore/sim
246246
`;
247247
248248
exports[`format > config > default > it formats ../cases/gjs/simple.gjs 1`] = `
249-
"<template>what</template>
249+
"<template>
250+
what
251+
</template>
250252
"
251253
`;
252254
@@ -724,6 +726,8 @@ export interface Signature {
724726
Yields: [];
725727
}
726728
727-
<template>what</template> as TemplateOnlyComponent<Signature>
729+
<template>
730+
what
731+
</template> as TemplateOnlyComponent<Signature>
728732
"
729733
`;

0 commit comments

Comments
 (0)