Skip to content

Commit

Permalink
Fix shorthand props not working inside expressions and update error h…
Browse files Browse the repository at this point in the history
…andling (#246)

* Fix shorthand props not working inside expressions

* Add changeset

* Also process children

* Improve typing and name

* More tests
  • Loading branch information
Princesseuh authored Aug 4, 2022
1 parent 1adfd4a commit 6ebc4d4
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-mangos-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'prettier-plugin-astro': minor
---

Update error handling to give better error messages when we fail to parse an expression, fixed shorthands props not working inside expressions
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-this-alias": "off",
"no-console": "warn",
"no-console": ["error", { "allow": ["warn", "error"] }],
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"],
"prettier/prettier": "warn"
Expand Down
91 changes: 81 additions & 10 deletions src/printer/embed.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { BuiltInParsers, Doc, ParserOptions } from 'prettier';
import _doc from 'prettier/doc';
import { SassFormatter, SassFormatterConfig } from 'sass-formatter';
import { AstPath, manualDedent, printFn, printRaw } from './utils';
import { anyNode, ExpressionNode } from './nodes';
import {
AstPath,
isNodeWithChildren,
isTagLikeNode,
manualDedent,
printFn,
printRaw,
} from './utils';

const {
builders: { group, indent, join, line, softline, hardline },
utils: { stripTrailingHardline },
utils: { stripTrailingHardline, mapDoc },
} = _doc;

type supportedStyleLang = 'css' | 'scss' | 'sass';
Expand All @@ -21,27 +30,43 @@ export function embed(
if (!node) return null;

if (node.type === 'expression') {
const textContent = printRaw(node);
// This is a bit of a hack, but I'm not sure how else to pass the original, pre-JSX transformations to the parser?
const originalContent = printRaw(node);
(opts as any).originalContent = originalContent;

let content: Doc;
const jsxNode = makeNodeJSXCompatible<ExpressionNode>(node);
const textContent = printRaw(jsxNode);

content = textToDoc(forceIntoExpression(textContent), {
let content: Doc;
content = textToDoc(textContent, {
...opts,
parser: expressionParser,
});

content = stripTrailingHardline(content);

return ['{', content, '}'];
// Create a Doc without the things we had to add to make the expression compatible with Babel
const astroDoc = mapDoc(content, (doc) => {
if (typeof doc === 'string' && doc.startsWith('__PRETTIER_SNIP__')) {
return '';
}

return doc;
});

return ['{', astroDoc, '}'];
}

// Attribute using an expression as value
if (node.type === 'attribute' && node.kind === 'expression') {
const value = node.value.trim();
const name = node.name.trim();
let attrNodeValue = textToDoc(forceIntoExpression(value), {

let attrNodeValue = textToDoc(value, {
...opts,
parser: expressionParser,
});

attrNodeValue = stripTrailingHardline(attrNodeValue);

if (name === value && opts.astroAllowShorthand) {
Expand Down Expand Up @@ -102,14 +127,60 @@ export function embed(
}

function expressionParser(text: string, parsers: BuiltInParsers, opts: ParserOptions) {
const ast = parsers.babel(text, opts);
let parsingResult;
const expressionContent = forceIntoExpression(text);

try {
parsingResult = parsers.babel(expressionContent, opts);
} catch (e: any) {
if (process.env.PRETTIER_DEBUG) {
throw e;
}

// If we couldn't parse the expression (ex: syntax error) and we return the result, Prettier will fail with a not
// very interesting error message (ex: unhandled node type 'expression'), as such we'll instead just return the unformatted result
console.error(e);

return (opts as any).originalContent;
}

return {
...ast,
program: ast.program.body[0].expression.children[0].expression,
...parsingResult,
program: parsingResult.program.body[0].expression.children[0].expression,
};
}

/**
* Due to the differences between Astro and JSX, Prettier's TypeScript parsers (be it `typescript`, `babel` or `babel-ts`)
* are not able to parse all expressions. A list of the difference that matters here:
* - Astro allows a shorthand syntax for props. ex: `<Component {props} />`
* - Astro allows multiple root elements. ex: `<div></div><div></div>`
*/
function makeNodeJSXCompatible<T>(node: any): T {
const newNode = { ...node };

if (isNodeWithChildren(newNode)) {
newNode.children.forEach((child) => {
if (isTagLikeNode(child)) {
child.attributes.forEach((attr) => {
// Transform shorthand attributes into their full format with a prefix so we can find them back
if (attr.kind === 'shorthand') {
attr.kind = 'expression';
attr.value = attr.name;
attr.name = '__PRETTIER_SNIP__' + attr.name;
}
});

if (isNodeWithChildren(child)) {
child = makeNodeJSXCompatible(child);
}
}
});
}

return newNode;
}

function forceIntoExpression(statement: string): string {
// note the trailing newline: if the statement ends in a // comment,
// we can't add the closing bracket right afterwards
Expand Down
18 changes: 18 additions & 0 deletions test/fixtures/other/shorthand-in-expression/input.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{enabled && <Header



{content} />}

{enabled && <Header


hello={hello}
{content} />}

{[].map((item) => {
return <>
<Header hello={hello} {content} />
<Header {content} />
</>})
}
12 changes: 12 additions & 0 deletions test/fixtures/other/shorthand-in-expression/output.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{enabled && <Header {content} />}

{enabled && <Header hello={hello} {content} />}

{[].map((item) => {
return (
<>
<Header hello={hello} {content} />
<Header {content} />
</>
);
})}
6 changes: 6 additions & 0 deletions test/tests/other.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,9 @@ test('Format binary expressions', files, 'other/binary-expression');
test('Format directives', files, 'other/directive');

test('Format slots', files, 'other/slots');

test(
'Can format expressions with shorthands props in them',
files,
'other/shorthand-in-expression'
);

0 comments on commit 6ebc4d4

Please sign in to comment.