diff --git a/changelog_unreleased/javascript/pr-8436.md b/changelog_unreleased/javascript/pr-8436.md new file mode 100644 index 000000000..fb9583905 --- /dev/null +++ b/changelog_unreleased/javascript/pr-8436.md @@ -0,0 +1,17 @@ +#### Support ES Module Attributes and JSON modules ([#8436](https://github.com/prettier/prettier/pull/8436) by [@fisker](https://github.com/fisker)) + +Support Stage-1 proposal [ES Module Attributes and JSON modules](https://github.com/tc39/proposal-module-attributes). + + +```js +// Input +import foo from "foo.json" with type: "json"; + +// Prettier stable +SyntaxError: Unexpected token, expected ";" (1:28) +> 1 | import foo from "foo.json" with type: "json"; + | ^ + +// Prettier master +import foo from "foo.json" with type: "json"; +``` diff --git a/src/language-js/parser-babel.js b/src/language-js/parser-babel.js index 588c6cab5..3f441b246 100644 --- a/src/language-js/parser-babel.js +++ b/src/language-js/parser-babel.js @@ -31,8 +31,9 @@ function babelOptions({ sourceType, extraPlugins = [] }) { "classPrivateMethods", "v8intrinsic", "partialApplication", - "privateIn", ["decorators", { decoratorsBeforeExport: false }], + "privateIn", + ["moduleAttributes", { version: "may-2020" }], ...extraPlugins, ], }; @@ -129,7 +130,16 @@ function rethrowSomeRecoveredErrors(ast) { for (const error of ast.errors) { if ( typeof error.message === "string" && - error.message.startsWith("Did not expect a type annotation here.") + (error.message.startsWith( + // UnexpectedTypeAnnotation + // https://github.com/babel/babel/blob/2f31ecf85d85cb100fa08d4d9a09de0fe4a117e4/packages/babel-parser/src/plugins/typescript/index.js#L88 + "Did not expect a type annotation here." + ) || + error.message.startsWith( + // ModuleAttributeDifferentFromType + // https://github.com/babel/babel/blob/bda759ac3dce548f021ca24e9182b6e6f7c218e3/packages/babel-parser/src/parser/location.js#L99 + "The only accepted module attribute is `type`" + )) ) { throw error; } diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index e659aa27c..84f6bc1f7 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -1022,11 +1022,16 @@ function printPathNoParens(path, options, print, args) { parts.push(" ", path.call(print, "source")); } + if (Array.isArray(n.attributes) && n.attributes.length !== 0) { + parts.push(" with ", concat(path.map(print, "attributes"))); + } + parts.push(semi); return concat(parts); } - + case "ImportAttribute": + return concat([path.call(print, "key"), ": ", path.call(print, "value")]); case "Import": return "import"; case "TSModuleBlock": diff --git a/tests/html/script/__snapshots__/jsfmt.spec.js.snap b/tests/html/script/__snapshots__/jsfmt.spec.js.snap index c93909bad..52ec843f5 100644 --- a/tests/html/script/__snapshots__/jsfmt.spec.js.snap +++ b/tests/html/script/__snapshots__/jsfmt.spec.js.snap @@ -117,6 +117,20 @@ async function foo() { ================================================================================ `; +exports[`module-attributes.html format 1`] = ` +====================================options===================================== +parsers: ["html"] +printWidth: 80 + | printWidth +=====================================input====================================== + + +=====================================output===================================== + + +================================================================================ +`; + exports[`script.html format 1`] = ` ====================================options===================================== parsers: ["html"] diff --git a/tests/html/script/module-attributes.html b/tests/html/script/module-attributes.html new file mode 100644 index 000000000..331944ba6 --- /dev/null +++ b/tests/html/script/module-attributes.html @@ -0,0 +1 @@ + diff --git a/tests/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap b/tests/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap index 914731348..5e8aa1b1c 100644 --- a/tests/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap +++ b/tests/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap @@ -720,6 +720,34 @@ obj.a.b &&= c; ================================================================================ `; +exports[`module-attributes-dynamic.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "babel-ts", "babel-flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +import("foo.json", { with: { type: "json" } }) + +=====================================output===================================== +import("foo.json", { with: { type: "json" } }); + +================================================================================ +`; + +exports[`module-attributes-static.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "babel-ts", "babel-flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +import foo from "foo.json" with type: "json"; + +=====================================output===================================== +import foo from "foo.json" with type: "json"; + +================================================================================ +`; + exports[`nullish-coalescing-operator.js format 1`] = ` ====================================options===================================== parsers: ["babel", "babel-ts", "babel-flow"] diff --git a/tests/js/babel-plugins/module-attributes-dynamic.js b/tests/js/babel-plugins/module-attributes-dynamic.js new file mode 100644 index 000000000..baa60fc43 --- /dev/null +++ b/tests/js/babel-plugins/module-attributes-dynamic.js @@ -0,0 +1 @@ +import("foo.json", { with: { type: "json" } }) diff --git a/tests/js/babel-plugins/module-attributes-static.js b/tests/js/babel-plugins/module-attributes-static.js new file mode 100644 index 000000000..fb31508a0 --- /dev/null +++ b/tests/js/babel-plugins/module-attributes-static.js @@ -0,0 +1 @@ +import foo from "foo.json" with type: "json"; diff --git a/tests/js/module-attributes/__snapshots__/jsfmt.spec.js.snap b/tests/js/module-attributes/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..43233aa6c --- /dev/null +++ b/tests/js/module-attributes/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`module-attributes-dynamic.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +import("foo.json", { with: { type: "json" } }) + +=====================================output===================================== +import("foo.json", { with: { type: "json" } }); + +================================================================================ +`; + +exports[`module-attributes-static.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +import foo from "foo.json" with type: "json"; + +=====================================output===================================== +import foo from "foo.json" with type: "json"; + +================================================================================ +`; diff --git a/tests/js/module-attributes/jsfmt.spec.js b/tests/js/module-attributes/jsfmt.spec.js new file mode 100644 index 000000000..8382eddeb --- /dev/null +++ b/tests/js/module-attributes/jsfmt.spec.js @@ -0,0 +1 @@ +run_spec(__dirname, ["babel"]); diff --git a/tests/js/module-attributes/module-attributes-dynamic.js b/tests/js/module-attributes/module-attributes-dynamic.js new file mode 100644 index 000000000..baa60fc43 --- /dev/null +++ b/tests/js/module-attributes/module-attributes-dynamic.js @@ -0,0 +1 @@ +import("foo.json", { with: { type: "json" } }) diff --git a/tests/js/module-attributes/module-attributes-static.js b/tests/js/module-attributes/module-attributes-static.js new file mode 100644 index 000000000..fb31508a0 --- /dev/null +++ b/tests/js/module-attributes/module-attributes-static.js @@ -0,0 +1 @@ +import foo from "foo.json" with type: "json"; diff --git a/tests/misc/errors/flow/__snapshots__/jsfmt.spec.js.snap b/tests/misc/errors/flow/__snapshots__/jsfmt.spec.js.snap index c9702a5ae..201643a34 100644 --- a/tests/misc/errors/flow/__snapshots__/jsfmt.spec.js.snap +++ b/tests/misc/errors/flow/__snapshots__/jsfmt.spec.js.snap @@ -6,3 +6,17 @@ exports[`dynamic-import.js error test 1`] = ` | ^ 2 | " `; + +exports[`module-attributes-dynamic.js error test 1`] = ` +"Unexpected token \`,\`, expected the token \`)\` (1:18) +> 1 | import(\\"foo.json\\", { with: { type: \\"json\\" } }) + | ^ + 2 | " +`; + +exports[`module-attributes-static.js error test 1`] = ` +"Unexpected token \`with\`, expected the token \`;\` (1:28) +> 1 | import foo from \\"foo.json\\" with type: \\"json\\"; + | ^^^^ + 2 | " +`; diff --git a/tests/misc/errors/flow/module-attributes-dynamic.js b/tests/misc/errors/flow/module-attributes-dynamic.js new file mode 100644 index 000000000..baa60fc43 --- /dev/null +++ b/tests/misc/errors/flow/module-attributes-dynamic.js @@ -0,0 +1 @@ +import("foo.json", { with: { type: "json" } }) diff --git a/tests/misc/errors/flow/module-attributes-static.js b/tests/misc/errors/flow/module-attributes-static.js new file mode 100644 index 000000000..fb31508a0 --- /dev/null +++ b/tests/misc/errors/flow/module-attributes-static.js @@ -0,0 +1 @@ +import foo from "foo.json" with type: "json"; diff --git a/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap b/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap index 463aec104..d3111bcef 100644 --- a/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap +++ b/tests/misc/errors/js/__snapshots__/jsfmt.spec.js.snap @@ -11,6 +11,13 @@ exports[`html-like-comments.js error test 1`] = ` 6 | " `; +exports[`module-attributes.js error test 1`] = ` +"The only accepted module attribute is \`type\` (1:33) +> 1 | import foo from \\"foo.json\\" with foo: \\"json\\"; + | ^ + 2 | " +`; + exports[`partial-template-strings.js error test 1`] = ` "Unexpected token (6:42) 4 | diff --git a/tests/misc/errors/js/module-attributes.js b/tests/misc/errors/js/module-attributes.js new file mode 100644 index 000000000..ecc33f5eb --- /dev/null +++ b/tests/misc/errors/js/module-attributes.js @@ -0,0 +1 @@ +import foo from "foo.json" with foo: "json"; diff --git a/tests/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap b/tests/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap index e3de5a098..8418d0d17 100644 --- a/tests/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap +++ b/tests/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap @@ -16,6 +16,20 @@ exports[`invalid-jsx-1.ts error test 1`] = ` 4 | " `; +exports[`module-attributes-dynamic.ts error test 1`] = ` +"Dynamic import must have one specifier as an argument. (1:8) +> 1 | import(\\"foo.json\\", { with: { type: \\"json\\" } }) + | ^ + 2 | " +`; + +exports[`module-attributes-static.ts error test 1`] = ` +"';' expected. (1:28) +> 1 | import foo from \\"foo.json\\" with type: \\"json\\"; + | ^ + 2 | " +`; + exports[`snippet: #0 error test 1`] = ` "Unexpected token. Did you mean \`{'}'}\` or \`}\`? (1:15) > 1 | let x1 =