Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prettier-plugin-organize-imports needs some help :) #1649

Open
benallfree opened this issue Sep 20, 2022 · 2 comments
Open

prettier-plugin-organize-imports needs some help :) #1649

benallfree opened this issue Sep 20, 2022 · 2 comments
Labels
question Further information is requested

Comments

@benallfree
Copy link

benallfree commented Sep 20, 2022

Hello maintainers, this is in reference to simonhaenisch/prettier-plugin-organize-imports#39 where we are trying to add Svelte support. A couple of us have tried unsuccessfully to understand how you do it here.

Can you help by providing a code sample (or add a Svelte test) that, given the string input of a Svelte file, returns the same thing with imports organized?

@jasonlyu123
Copy link
Member

jasonlyu123 commented Sep 20, 2022

There isn't a public API for it. Our code structure is quite different from volar. There isn't a language-service package to be reused. You might have to do more work for it, unfortunately. But I would assume that organize-import might not need as much of the logic we have in the language service host layer. It should not be very difficult.

What we did is:

  1. we implement the readFile of the language service host with a transformation with svelte2tsx. It also returns sourcemap info for the next step.

    const tsx = svelte2tsx(text, {
    filename: document.getFilePath() ?? undefined,
    isTsFile: options.useNewTransformation
    ? scriptKind === ts.ScriptKind.TS
    : scriptKind === ts.ScriptKind.TSX,
    mode: options.useNewTransformation ? 'ts' : 'tsx',
    typingsNamespace: options.useNewTransformation ? options.typingsNamespace : undefined,
    emitOnTemplateError: options.transformOnTemplateError,
    namespace: document.config?.compilerOptions?.namespace,
    accessors:
    document.config?.compilerOptions?.accessors ??
    document.config?.compilerOptions?.customElement
    });
    text = tsx.code;
    tsxMap = tsx.map as EncodedSourceMap;

    The mode option we used here is 'ts' by default. It might also be easier for you as it won't need jsx configs in the tsconfig, the namespace is svelteHTML but it probably won't matter much for your use case. emitOnTemplateError should be true. accessors didn't matter much.

  2. run the organize-import from the ts language service and sourcemap the result back to the original file. By the start and end of the range. And then apply some logic to fix the indent and the possible sourcemap errors.

    const changes = lang.organizeImports(
    {
    fileName: tsDoc.filePath,
    type: 'file',
    skipDestructiveCodeActions
    },
    {
    ...(await this.configManager.getFormatCodeSettingsForFile(
    document,
    tsDoc.scriptKind
    )),
    // handle it on our own
    baseIndentSize: undefined
    },
    userPreferences
    );
    const documentChanges = await Promise.all(
    changes.map(async (change) => {
    // Organize Imports will only affect the current file, so no need to check the file path
    return TextDocumentEdit.create(
    OptionalVersionedTextDocumentIdentifier.create(document.url, null),
    change.textChanges.map((edit) => {
    const range = this.checkRemoveImportCodeActionRange(
    edit,
    tsDoc,
    mapRangeToOriginal(tsDoc, convertRange(tsDoc, edit.span))
    );
    return this.fixIndentationOfImports(
    TextEdit.replace(range, edit.newText),
    document
    );
    })
    );
    })
    );

    The indentation part can probably be replaced with a ts.FormatCodeSettings.baseIndent option. We didn't do it for some historical reason and also that makes us able to detect existing indent, Which probably isn't needed for a prettier plugin.

As for tests, you could find them here. Setup isn't important for your use case. You can just read the corresponding file and the result text edit. Also as I mentioned earlier the indent might not be super important for a prettier plugin.

it('organizes imports', async () => {
const { provider, document } = setup('codeactions.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
// eslint-disable-next-line max-len
newText:
"import { A } from 'bla';\nimport { C } from 'blubb';\n",
range: {
start: {
character: 0,
line: 1
},
end: {
character: 0,
line: 2
}
}
},
{
newText: '',
range: {
start: {
character: 0,
line: 2
},
end: {
character: 0,
line: 3
}
}
},
{
newText: '',
range: {
start: {
character: 0,
line: 3
},
end: {
character: 0,
line: 4
}
}
},
{
newText: '',
range: {
start: {
character: 0,
line: 4
},
end: {
character: 0,
line: 5
}
}
}
],
textDocument: {
uri: getUri('codeactions.svelte'),
version: null
}
}
]
},
kind: CodeActionKind.SourceOrganizeImports,
title: 'Organize Imports'
}
]);
});
it('sort imports', async () => {
const { provider, document } = setup('codeactions.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [SORT_IMPORT_CODE_ACTION_KIND]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
// eslint-disable-next-line max-len
newText:
"import { A, B } from 'bla';\n" +
"import { C } from 'blubb';\n" +
"import { D } from 'd';\n",
range: {
start: {
character: 0,
line: 1
},
end: {
character: 0,
line: 2
}
}
},
{
newText: '',
range: {
start: {
character: 0,
line: 2
},
end: {
character: 0,
line: 3
}
}
},
{
newText: '',
range: {
start: {
character: 0,
line: 3
},
end: {
character: 0,
line: 4
}
}
},
{
newText: '',
range: {
start: {
character: 0,
line: 4
},
end: {
character: 0,
line: 5
}
}
}
],
textDocument: {
uri: getUri('codeactions.svelte'),
version: null
}
}
]
},
kind: SORT_IMPORT_CODE_ACTION_KIND,
title: 'Sort Imports'
}
]);
});
it('organizes imports with module script', async () => {
const { provider, document } = setup('organize-imports-with-module.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
newText: "import { c } from './c';\n",
range: {
start: {
line: 1,
character: 2
},
end: {
line: 2,
character: 0
}
}
},
{
newText: "import A from './A';\n",
range: {
start: {
line: 6,
character: 2
},
end: {
line: 7,
character: 2
}
}
},
{
newText: '',
range: {
start: {
line: 7,
character: 2
},
end: {
line: 8,
character: 0
}
}
}
],
textDocument: {
uri: getUri('organize-imports-with-module.svelte'),
version: null
}
}
]
},
kind: CodeActionKind.SourceOrganizeImports,
title: 'Organize Imports'
}
]);
});
it('organizes imports with module script and store', async () => {
const { provider, document } = setup('organize-imports-module-store.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
newText:
"import { _d } from 'svelte-i18n';\n import { _e } from 'svelte-i18n1';\n",
range: {
end: {
character: 2,
line: 6
},
start: {
character: 2,
line: 5
}
}
},
{
newText: '',
range: {
end: {
character: 2,
line: 7
},
start: {
character: 2,
line: 6
}
}
},
{
newText: '',
range: {
start: {
character: 2,
line: 7
},
end: {
character: 0,
line: 8
}
}
}
],
textDocument: {
uri: getUri('organize-imports-module-store.svelte'),
version: null
}
}
]
},
kind: CodeActionKind.SourceOrganizeImports,
title: 'Organize Imports'
}
]);
});
it('organizes imports which changes nothing (one import)', async () => {
const { provider, document } = setup('organize-imports-unchanged1.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
newText: "import { c } from './c';\n",
range: {
end: {
character: 0,
line: 2
},
start: {
character: 2,
line: 1
}
}
}
],
textDocument: {
uri: getUri('organize-imports-unchanged1.svelte'),
version: null
}
}
]
},
kind: 'source.organizeImports',
title: 'Organize Imports'
}
]);
});
it('organizes imports which changes nothing (two imports)', async () => {
const { provider, document } = setup('organize-imports-unchanged2.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
newText:
"import { c } from './c';\n import { d } from './d';\n",
range: {
end: {
character: 0,
line: 2
},
start: {
character: 2,
line: 1
}
}
},
{
newText: '',
range: {
end: {
character: 0,
line: 3
},
start: {
character: 0,
line: 2
}
}
}
],
textDocument: {
uri: getUri('organize-imports-unchanged2.svelte'),
version: null
}
}
]
},
kind: 'source.organizeImports',
title: 'Organize Imports'
}
]);
});
it('organizes imports and not remove the leading comment', async () => {
const { provider, document } = setup('organize-imports-leading-comment.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
newText: "import { } from './t.png';\n",
range: {
end: {
character: 0,
line: 2
},
start: {
character: 4,
line: 1
}
}
},
{
newText: "import { } from './somepng.png';\n",
range: {
end: {
character: 0,
line: 4
},
start: {
character: 4,
line: 3
}
}
}
],
textDocument: {
uri: getUri('organize-imports-leading-comment.svelte'),
version: null
}
}
]
},
kind: 'source.organizeImports',
title: 'Organize Imports'
}
]);
});
it('organize imports should do nothing if there is a parser error', async () => {
const { provider, document } = setup('organize-imports-error.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
assert.deepStrictEqual(codeActions, []);
});
it('organize imports aware of groups', async () => {
const { provider, document } = setup('organize-imports-group.svelte');
const codeActions = await provider.getCodeActions(
document,
Range.create(Position.create(1, 4), Position.create(1, 5)),
{
diagnostics: [],
only: [CodeActionKind.SourceOrganizeImports]
}
);
(<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
(edit) => (edit.newText = harmonizeNewLines(edit.newText))
);
assert.deepStrictEqual(codeActions, [
{
edit: {
documentChanges: [
{
edits: [
{
newText:
"import { } from 'svelte/transition';\n" +
`${indent}import { } from './codeaction-checkJs.svelte';\n`,
range: {
end: {
character: 4,
line: 4
},
start: {
character: 4,
line: 3
}
}
},
{
newText: '',
range: {
end: {
character: 0,
line: 5
},
start: {
character: 4,
line: 4
}
}
}
],
textDocument: {
uri: getUri('organize-imports-group.svelte'),
version: null
}
}
]
},
kind: 'source.organizeImports',
title: 'Organize Imports'
}
]);
});

Hope that helps you. Feel free to ask more if you have some trouble understanding.

@benallfree
Copy link
Author

Thank you so much!

@jasonlyu123 jasonlyu123 added the question Further information is requested label Feb 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants