From 920639a8de97a5bea76a8eb726cedc5a608c45bb Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Tue, 25 Jun 2024 14:11:51 -0400 Subject: [PATCH] Add support for import attributes (#174) There are three styles of import assertions/attributes that have been proposed over the years and made it to various stages of TC-39. Here's a bit of [history](https://github.com/tc39/proposal-import-attributes#history): 1) `import x from 'x' with type: "json"` - original proposal, made it to stage 2 2) `import x from 'x' assert { type: 'json' };` - import "assertion", got to stage 3, then found problems 3) `import x from 'x' with { type: 'json' };` - import "attribute", current proposal, stage 3 Babel only supports these with the use of a parser plugin, either `importAttributes` or `importAssertions`. Also, babel/generator has to be told which style to generate. Upstream has a PR, https://github.com/trivago/prettier-plugin-sort-imports/pull/273/files, which adds another user-level option to control this. Instead, I've taken the stance here that we should only generate the latest "import attribute" style code. So, not only does this PR add support for import attributes/assertions, this has the side-effect of converting deprecated import assertions to import attributes. Maybe there's some reason someone would want to stick with the old style, but I can't think of one, and I personally would appreciate getting updated to the latest format. --- src/utils/__tests__/get-code-from-ast.spec.ts | 25 +++++++++++++++++++ src/utils/get-code-from-ast.ts | 2 +- tests/Babel/__snapshots__/ppsi.spec.ts.snap | 13 ++++++++++ tests/Babel/imports-with-assertions.ts | 4 +++ tests/Babel/ppsi.spec.ts | 1 + .../__snapshots__/ppsi.spec.ts.snap | 13 ++++++++++ tests/Typescript/imports-with-assertions.ts | 4 +++ tests/Typescript/ppsi.spec.ts | 2 +- 8 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/Babel/imports-with-assertions.ts create mode 100644 tests/Typescript/imports-with-assertions.ts diff --git a/src/utils/__tests__/get-code-from-ast.spec.ts b/src/utils/__tests__/get-code-from-ast.spec.ts index db1303b..50b316c 100644 --- a/src/utils/__tests__/get-code-from-ast.spec.ts +++ b/src/utils/__tests__/get-code-from-ast.spec.ts @@ -71,3 +71,28 @@ import z from "z"; `, ); }); + +test('handles import attributes and assertions, converting to attributes when necessary', async () => { + const code = `import z from 'z'; + import g from 'g' with { type: 'json' }; +import c from 'c' assert { type: 'json' }; +`; + const importNodes = getImportNodes(code, { + plugins: [['importAttributes', { deprecatedAssertSyntax: true }]], + }); + const sortedNodes = getSortedNodes(importNodes, { + importOrder: defaultImportOrder, + importOrderCombineTypeAndValueImports: true, + }); + const formatted = getCodeFromAst({ + nodesToOutput: sortedNodes, + originalCode: code, + directives: [], + }); + expect(await format(formatted, { parser: 'babel' })).toEqual( + `import c from "c" with { type: "json" }; +import g from "g" with { type: "json" }; +import z from "z"; +`, + ); +}); diff --git a/src/utils/get-code-from-ast.ts b/src/utils/get-code-from-ast.ts index a86a35d..6ddd357 100644 --- a/src/utils/get-code-from-ast.ts +++ b/src/utils/get-code-from-ast.ts @@ -73,7 +73,7 @@ export const getCodeFromAst = ({ }, }); - const { code } = generate(newAST); + const { code } = generate(newAST, { importAttributesKeyword: 'with' }); const replacedCode = code.replace(injectNewlinesRegex, newLineCharacters); diff --git a/tests/Babel/__snapshots__/ppsi.spec.ts.snap b/tests/Babel/__snapshots__/ppsi.spec.ts.snap index 5ee8db8..e21cd09 100644 --- a/tests/Babel/__snapshots__/ppsi.spec.ts.snap +++ b/tests/Babel/__snapshots__/ppsi.spec.ts.snap @@ -1,5 +1,18 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`imports-with-assertions.ts - babel-verify > imports-with-assertions.ts 1`] = ` +import z from 'z-assert' assert { type: 'json' }; +import x from 'x-with' with { type: 'json' }; + +// import y from 'y-legacy' with type: "json" // <-- this format is from a very old proposal, and is not supported +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +import x from "x-with" with { type: "json" }; +import z from "z-assert" with { type: "json" }; + +// import y from 'y-legacy' with type: "json" // <-- this format is from a very old proposal, and is not supported + +`; + exports[`imports-with-comments.js - babel-verify > imports-with-comments.js 1`] = ` // I am top level comment in this file. diff --git a/tests/Babel/imports-with-assertions.ts b/tests/Babel/imports-with-assertions.ts new file mode 100644 index 0000000..2ac8b9c --- /dev/null +++ b/tests/Babel/imports-with-assertions.ts @@ -0,0 +1,4 @@ +import z from 'z-assert' assert { type: 'json' }; +import x from 'x-with' with { type: 'json' }; + +// import y from 'y-legacy' with type: "json" // <-- this format is from a very old proposal, and is not supported diff --git a/tests/Babel/ppsi.spec.ts b/tests/Babel/ppsi.spec.ts index 5182a94..6780ebc 100644 --- a/tests/Babel/ppsi.spec.ts +++ b/tests/Babel/ppsi.spec.ts @@ -2,4 +2,5 @@ import {run_spec} from '../../test-setup/run_spec'; run_spec(__dirname, ["babel"], { importOrder: [ "", '^@core/(.*)$', '^@server/(.*)', '^@ui/(.*)$', '^[./]'], + importOrderParserPlugins : ['[\"importAttributes\", {\"deprecatedAssertSyntax\": true}]'], }); diff --git a/tests/Typescript/__snapshots__/ppsi.spec.ts.snap b/tests/Typescript/__snapshots__/ppsi.spec.ts.snap index 554336f..f34c00d 100644 --- a/tests/Typescript/__snapshots__/ppsi.spec.ts.snap +++ b/tests/Typescript/__snapshots__/ppsi.spec.ts.snap @@ -105,6 +105,19 @@ export class AppComponent extends BaseComponent { `; +exports[`imports-with-assertions.ts - typescript-verify > imports-with-assertions.ts 1`] = ` +import z from 'z-assert' assert { type: 'json' }; +import x from 'x-with' with { type: 'json' }; + +// import y from 'y-legacy' with type: "json" // <-- this format is from a very old proposal, and is not supported +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +import x from "x-with" with { type: "json" }; +import z from "z-assert" with { type: "json" }; + +// import y from 'y-legacy' with type: "json" // <-- this format is from a very old proposal, and is not supported + +`; + exports[`imports-with-comments.ts - typescript-verify > imports-with-comments.ts 1`] = ` import z from 'z'; import { isEmpty } from "lodash-es"; diff --git a/tests/Typescript/imports-with-assertions.ts b/tests/Typescript/imports-with-assertions.ts new file mode 100644 index 0000000..2ac8b9c --- /dev/null +++ b/tests/Typescript/imports-with-assertions.ts @@ -0,0 +1,4 @@ +import z from 'z-assert' assert { type: 'json' }; +import x from 'x-with' with { type: 'json' }; + +// import y from 'y-legacy' with type: "json" // <-- this format is from a very old proposal, and is not supported diff --git a/tests/Typescript/ppsi.spec.ts b/tests/Typescript/ppsi.spec.ts index ca4b436..a4d851e 100644 --- a/tests/Typescript/ppsi.spec.ts +++ b/tests/Typescript/ppsi.spec.ts @@ -2,5 +2,5 @@ import {run_spec} from '../../test-setup/run_spec'; run_spec(__dirname, ["typescript"], { importOrder: ['^@core/(.*)$', '^@server/(.*)', '^@ui/(.*)$', '^[./]'], - importOrderParserPlugins : ['typescript', 'jsx', 'decorators-legacy', 'classProperties'], + importOrderParserPlugins : ['typescript', 'jsx', 'decorators-legacy', 'classProperties', '[\"importAttributes\", {\"deprecatedAssertSyntax\": true}]'], });