diff --git a/src/language-js/clean.js b/src/language-js/clean.js index 9ce72c116..73b4bd196 100644 --- a/src/language-js/clean.js +++ b/src/language-js/clean.js @@ -122,6 +122,24 @@ function clean(ast, newObj, parent) { newObj.value.expression.quasis.forEach(q => delete q.value); } + // CSS template literals in Angular Component decorator + if ( + ast.type === "Decorator" && + ast.expression.type === "CallExpression" && + ast.expression.callee.name === "Component" && + ast.expression.arguments.length === 1 && + ast.expression.arguments[0].properties.some( + prop => + prop.key.name === "styles" && prop.value.type === "ArrayExpression" + ) + ) { + newObj.expression.arguments[0].properties.forEach(prop => { + if (prop.value.type === "ArrayExpression") { + prop.value.elements[0].quasis.forEach(q => delete q.value); + } + }); + } + // styled-components, graphql, markdown if ( ast.type === "TaggedTemplateExpression" && diff --git a/src/language-js/embed.js b/src/language-js/embed.js index 965162d8f..5cee8f40b 100644 --- a/src/language-js/embed.js +++ b/src/language-js/embed.js @@ -18,9 +18,12 @@ function embed(path, print, textToDoc /*, options */) { switch (node.type) { case "TemplateLiteral": { - const isCss = [isStyledJsx, isStyledComponents, isCssProp].some(isIt => - isIt(path) - ); + const isCss = [ + isStyledJsx, + isStyledComponents, + isCssProp, + isAngularComponentStyles + ].some(isIt => isIt(path)); if (isCss) { // Get full template literal with expressions replaced by placeholders @@ -176,6 +179,18 @@ function embed(path, print, textToDoc /*, options */) { } } +function isPropertyWithinAngularComponentDecorator(path, parentIndexToCheck) { + const parent = path.getParentNode(parentIndexToCheck); + return !!( + parent && + parent.type === "Decorator" && + parent.expression && + parent.expression.type === "CallExpression" && + parent.expression.callee && + parent.expression.callee.name === "Component" + ); +} + function getIndentation(str) { const firstMatchedIndent = str.match(/^([^\S\n]*)\S/m); return firstMatchedIndent === null ? "" : firstMatchedIndent[1]; @@ -334,6 +349,41 @@ function isStyledJsx(path) { ); } +/** + * Angular Components can have: + * - Inline HTML template + * - Inline CSS styles + * + * ...which are both within template literals somewhere + * inside of the Component decorator factory. + * + * TODO: Format HTML template once prettier's HTML + * formatting is "ready" + * + * E.g. + * @Component({ + * template: `
...
`, + * styles: [`h1 { color: blue; }`] + * }) + */ +function isAngularComponentStyles(path) { + const parent = path.getParentNode(); + const parentParent = path.getParentNode(1); + const isWithinArrayValueFromProperty = !!( + parent && + (parent.type === "ArrayExpression" && parentParent.type === "Property") + ); + if ( + isWithinArrayValueFromProperty && + isPropertyWithinAngularComponentDecorator(path, 4) + ) { + if (parentParent.key && parentParent.key.name === "styles") { + return true; + } + } + return false; +} + /** * styled-components template literals */ diff --git a/tests/angular_component_examples/__snapshots__/jsfmt.spec.js.snap b/tests/angular_component_examples/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..27efe93ba --- /dev/null +++ b/tests/angular_component_examples/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`test.component.ts 1`] = ` +@Component({ + selector: 'app-test', + template: \` + \`, + styles: [ \` + + :host { + color: red; + } + div { background: blue + } +\` + +] +}) +class TestComponent {} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +@Component({ + selector: "app-test", + template: \` + \`, + styles: [ + \` + :host { + color: red; + } + div { + background: blue; + } + \` + ] +}) +class TestComponent {} + +`; + +exports[`test.component.ts 2`] = ` +@Component({ + selector: 'app-test', + template: \` + \`, + styles: [ \` + + :host { + color: red; + } + div { background: blue + } +\` + +] +}) +class TestComponent {} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +@Component({ + selector: "app-test", + template: \` + \`, + styles: [ + \` + :host { + color: red; + } + div { + background: blue; + } + \`, + ], +}) +class TestComponent {} + +`; diff --git a/tests/angular_component_examples/jsfmt.spec.js b/tests/angular_component_examples/jsfmt.spec.js new file mode 100644 index 000000000..ecbf77cc1 --- /dev/null +++ b/tests/angular_component_examples/jsfmt.spec.js @@ -0,0 +1,2 @@ +run_spec(__dirname, ["typescript"]); +run_spec(__dirname, ["typescript"], { trailingComma: "es5" }); diff --git a/tests/angular_component_examples/test.component.ts b/tests/angular_component_examples/test.component.ts new file mode 100644 index 000000000..e83eea6fd --- /dev/null +++ b/tests/angular_component_examples/test.component.ts @@ -0,0 +1,17 @@ +@Component({ + selector: 'app-test', + template: ` + `, + styles: [ ` + + :host { + color: red; + } + div { background: blue + } +` + +] +}) +class TestComponent {}