Skip to content

Commit ec5c9c3

Browse files
authored
Merge pull request #30 from gitKrystan/detect-pre-preprocessing
Don't convert back to <template> if text is pre-preprocessed
2 parents 8544c1d + 542c74a commit ec5c9c3

17 files changed

Lines changed: 8187 additions & 1213 deletions

File tree

src/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ const languages: SupportLanguage[] = [
1414
},
1515
];
1616

17-
const parsers: Record<string, Parser<BaseNode>> = {
17+
const parsers: Record<string, Parser<BaseNode | undefined>> = {
1818
[PARSER_NAME]: parser,
1919
};
2020

21-
const printers: Record<string, Printer<BaseNode>> = {
21+
const printers: Record<string, Printer<BaseNode | undefined>> = {
2222
[PRINTER_NAME]: printer,
2323
};
2424

25-
const plugin: Plugin<BaseNode> = {
25+
const plugin: Plugin<BaseNode | undefined> = {
2626
languages,
2727
parsers,
2828
printers,

src/options.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import type {
66

77
import type { BaseNode } from './types/ast';
88

9-
export interface Options extends ParserOptions<BaseNode> {
9+
export interface Options extends ParserOptions<BaseNode | undefined> {
1010
templateExportDefault?: boolean;
1111
templateSingleQuote?: boolean;
12+
__inputWasPreprocessed?: boolean;
1213
}
1314

1415
const templateExportDefault: BooleanSupportOption = {
@@ -39,7 +40,16 @@ const templateSingleQuote: BooleanSupportOption = {
3940
'Use single quotes instead of double quotes within template tags.',
4041
};
4142

43+
const __inputWasPreprocessed: BooleanSupportOption = {
44+
since: '0.1.0',
45+
category: 'Format',
46+
type: 'boolean',
47+
description:
48+
'Internal: If true, the template was preprocessed before being run through Prettier.',
49+
};
50+
4251
export const options: SupportOptions = {
4352
templateExportDefault,
4453
templateSingleQuote,
54+
__inputWasPreprocessed,
4555
};

src/parse.ts

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { NodePath } from '@babel/core';
22
import { traverse } from '@babel/core';
3-
import type { Node } from '@babel/types';
3+
import type { Node, TemplateLiteral } from '@babel/types';
44
import {
55
isBinaryExpression,
66
isMemberExpression,
@@ -19,37 +19,54 @@ import {
1919
import type { Options } from './options';
2020
import { definePrinter } from './print/index';
2121
import type { BaseNode } from './types/ast';
22+
import type {
23+
GlimmerExpressionExtra,
24+
GlimmerTemplateExtra,
25+
} from './types/glimmer';
26+
import type {
27+
RawGlimmerArrayExpression,
28+
RawGlimmerClassProperty,
29+
} from './types/raw';
2230
import {
23-
hasGlimmerArrayExpression,
31+
hasRawGlimmerArrayExpression,
2432
isRawGlimmerArrayExpression,
2533
isRawGlimmerCallExpression,
2634
isRawGlimmerClassProperty,
2735
} from './types/raw';
2836
import { hasAmbiguousNextLine } from './utils/ambiguity';
29-
import { assert } from './utils/index';
37+
import { assert, squish } from './utils/index';
3038

31-
const typescript = babelParsers['babel-ts'] as Parser<BaseNode>;
39+
const typescript = babelParsers['babel-ts'] as Parser<BaseNode | undefined>;
3240

33-
const preprocess: Required<Parser<BaseNode>>['preprocess'] = (
34-
text,
35-
options
41+
const preprocess: Required<Parser<BaseNode | undefined>>['preprocess'] = (
42+
text: string,
43+
options: Options
3644
) => {
37-
const preprocessed = preprocessEmbeddedTemplates(text, {
38-
getTemplateLocals,
39-
40-
templateTag: TEMPLATE_TAG_NAME,
41-
templateTagReplacement: TEMPLATE_TAG_PLACEHOLDER,
42-
43-
includeSourceMaps: false,
44-
includeTemplateTokens: false,
45-
46-
relativePath: options.filepath,
47-
}).output;
45+
let preprocessed: string;
46+
if (text.includes(TEMPLATE_TAG_PLACEHOLDER)) {
47+
// This happens when Prettier is being run via eslint + eslint-plugin-ember
48+
// See https://github.com/ember-cli/eslint-plugin-ember/issues/1659
49+
options.__inputWasPreprocessed = true;
50+
preprocessed = text;
51+
} else {
52+
options.__inputWasPreprocessed = false;
53+
preprocessed = preprocessEmbeddedTemplates(text, {
54+
getTemplateLocals,
55+
56+
templateTag: TEMPLATE_TAG_NAME,
57+
templateTagReplacement: TEMPLATE_TAG_PLACEHOLDER,
58+
59+
includeSourceMaps: false,
60+
includeTemplateTokens: false,
61+
62+
relativePath: options.filepath,
63+
}).output;
64+
}
4865

4966
return desugarDefaultExportTemplates(preprocessed);
5067
};
5168

52-
export const parser: Parser<BaseNode> = {
69+
export const parser: Parser<BaseNode | undefined> = {
5370
...typescript,
5471
astFormat: PRINTER_NAME,
5572

@@ -68,11 +85,12 @@ export const parser: Parser<BaseNode> = {
6885
traverse(ast as Node, {
6986
enter: makeEnter(options),
7087
});
88+
assert('expected ast', ast);
7189
return ast;
7290
},
7391
};
7492

75-
function makeEnter(options: Options) {
93+
function makeEnter(options: Options): (path: NodePath) => void {
7694
return (path: NodePath) => {
7795
const node = path.node;
7896
const parentNode = path.parentPath?.node;
@@ -85,38 +103,60 @@ function makeEnter(options: Options) {
85103
throw new SyntaxError('Ember <template> tag used as an object property.');
86104
} else if (
87105
isBinaryExpression(node) &&
88-
(hasGlimmerArrayExpression(node.left) ||
89-
hasGlimmerArrayExpression(node.right))
106+
(hasRawGlimmerArrayExpression(node.left) ||
107+
hasRawGlimmerArrayExpression(node.right))
90108
) {
91109
throw new SyntaxError('Ember <template> tag used in binary expression.');
92110
} else if (
93111
isTaggedTemplateExpression(node) &&
94-
hasGlimmerArrayExpression(node.tag)
112+
hasRawGlimmerArrayExpression(node.tag)
95113
) {
96114
throw new SyntaxError(
97115
'Ember <template> tag used as tagged template expression.'
98116
);
99117
} else if (
100118
isMemberExpression(node) &&
101-
hasGlimmerArrayExpression(node.object)
119+
hasRawGlimmerArrayExpression(node.object)
102120
) {
103121
throw new SyntaxError('Ember <template> tag used as member expression.');
104122
}
105123

106-
if (isRawGlimmerArrayExpression(node) || isRawGlimmerClassProperty(node)) {
107-
const extra = {
108-
hasGlimmerExpression: true,
109-
forceSemi: hasAmbiguousNextLine(path, options),
110-
};
111-
if (typeof node.extra === 'object') {
112-
node.extra = { ...node.extra, ...extra };
113-
} else {
114-
node.extra = extra;
115-
}
124+
if (isRawGlimmerArrayExpression(node)) {
125+
tagGlimmerExpression(node, hasAmbiguousNextLine(path, options));
126+
tagGlimmerTemplate(node.elements[0].arguments[0]);
127+
} else if (isRawGlimmerClassProperty(node)) {
128+
tagGlimmerExpression(node, hasAmbiguousNextLine(path, options));
129+
tagGlimmerTemplate(node.key.arguments[0]);
116130
}
117131
};
118132
}
119133

134+
function tagGlimmerExpression(
135+
node: RawGlimmerArrayExpression | RawGlimmerClassProperty,
136+
forceSemi: boolean
137+
): void {
138+
const extra: GlimmerExpressionExtra = {
139+
hasGlimmerExpression: true,
140+
forceSemi,
141+
};
142+
if (typeof node.extra === 'object') {
143+
node.extra = { ...node.extra, ...extra };
144+
} else {
145+
node.extra = extra;
146+
}
147+
}
148+
149+
function tagGlimmerTemplate(node: TemplateLiteral): void {
150+
const extra: GlimmerTemplateExtra = {
151+
isGlimmerTemplate: true,
152+
};
153+
if (typeof node.extra === 'object') {
154+
node.extra = { ...node.extra, ...extra };
155+
} else {
156+
node.extra = extra;
157+
}
158+
}
159+
120160
/**
121161
* Desugar template tag default exports because they parse as
122162
* ExpressionStatement, which has a bunch of irrelevant custom semicolon
@@ -129,11 +169,11 @@ function makeEnter(options: Options) {
129169
function desugarDefaultExportTemplates(preprocessed: string): string {
130170
const placeholderOpen = `[${TEMPLATE_TAG_PLACEHOLDER}`; // intentionally missing ]
131171

132-
// ^\s*(\()?\s*\[__GLIMMER_TEMPLATE
172+
// (^|;)\s*(\()?\s*\[__GLIMMER_TEMPLATE
133173
const sugaredDefaultExport = new RegExp(
134-
`^\\s*(\\()?\\s*\\${placeholderOpen}`
174+
`(^|;)\\s*(\\()?\\s*\\${placeholderOpen}`
135175
);
136-
const desugaredDefaultExport = `export default $1${placeholderOpen}`;
176+
const desugaredDefaultExport = `$1 export default $2${placeholderOpen}`;
137177

138178
const lines = preprocessed.split(/\r?\n/);
139179
const desugaredLines: string[] = [];
@@ -151,7 +191,10 @@ function desugarDefaultExportTemplates(preprocessed: string): string {
151191

152192
assert('expected non-negative blockLevel', blockLevel > -1);
153193

154-
if (!previousLine?.includes('prettier-ignore') && blockLevel === 0) {
194+
const previousLineIsPrettierIgnore =
195+
previousLine && squish(previousLine) === '// prettier-ignore';
196+
197+
if (!previousLineIsPrettierIgnore && blockLevel === 0) {
155198
line = line.replace(sugaredDefaultExport, desugaredDefaultExport);
156199
}
157200

0 commit comments

Comments
 (0)