Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/sunny-buttons-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astrojs/language-server': patch
'astro-vscode': patch
---

Fixes AstroComponent suffix not being removed from Svelte and Vue imports in .astro files
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { CompletionItemKind } from '@volar/language-server';
import { URI } from 'vscode-uri';
import { AstroVirtualCode } from '../../core/index.js';
import {
getAlreadyImportedAstroComponentSources,
isAstroComponentImportSource,
getAlreadyImportedComponentSources,
isComponentImportSource,
mapEdit,
rewriteAstroImportText,
rewriteComponentImportText,
stripAstroComponentSuffix,
} from './utils.js';

Expand All @@ -19,7 +19,7 @@ export function enhancedProvideCompletionItems(
completions: CompletionList,
documentText: string,
): CompletionList {
const importedAstroSources = getAlreadyImportedAstroComponentSources(ts, documentText);
const importedComponentSources = getAlreadyImportedComponentSources(ts, documentText);

completions.items = completions.items
.filter((completion) => {
Expand All @@ -28,7 +28,7 @@ export function enhancedProvideCompletionItems(
}

const source = completion?.data?.originalItem?.source;
return !(source && importedAstroSources.has(source));
return !(source && importedComponentSources.has(source));
})
.map((completion) => {
const source = completion?.data?.originalItem?.source;
Expand All @@ -39,15 +39,12 @@ export function enhancedProvideCompletionItems(
}

// For components import, use the file kind and sort them first, as they're often what the user want over something else
if (['.astro', '.svelte', '.vue'].some((ext) => source.endsWith(ext))) {
if (isComponentImportSource(source)) {
completion.kind = CompletionItemKind.File;
completion.detail = completion.detail + '\n\n' + source;
completion.sortText = '\u0001' + (completion.sortText ?? completion.label);
completion.data.isComponent = true;

if (isAstroComponentImportSource(source)) {
rewriteAstroComponentCompletion(completion);
}
rewriteAstroComponentCompletion(completion);
}
}

Expand All @@ -69,7 +66,7 @@ export function enhancedResolveCompletionItem(
);
}

if (isAstroComponentImportSource(resolvedCompletion.data.originalItem.source)) {
if (isComponentImportSource(resolvedCompletion.data.originalItem.source)) {
rewriteAstroComponentCompletion(resolvedCompletion);
}

Expand Down Expand Up @@ -104,7 +101,7 @@ function rewriteAstroComponentCompletion(completion: CompletionItem) {
if (completion.additionalTextEdits) {
completion.additionalTextEdits = completion.additionalTextEdits.map((edit) => ({
...edit,
newText: rewriteAstroImportText(edit.newText),
newText: rewriteComponentImportText(edit.newText),
}));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import type { AstroVirtualCode } from '../../core/index.js';
import { editShouldBeInFrontmatter, ensureProperEditForFrontmatter } from '../utils.js';

const ASTRO_COMPONENT_SUFFIX = 'AstroComponent';
const ASTRO_IMPORT_FROM_PATTERN = /\bfrom\s+['"][^'"]+\.astro['"]/;
const COMPONENT_IMPORT_FROM_PATTERN = /\bfrom\s+['"][^'"]+\.(?:astro|svelte|vue)['"]/;
const ASTRO_DEFAULT_IMPORT_PATTERN =
/^(\s*import(?:\s+type)?\s+)([A-Za-z_$][\w$]*)AstroComponent(?=\s*,|\s+from\b)/;
const ASTRO_DEFAULT_ALIAS_PATTERN = /(default\s+as\s+)([A-Za-z_$][\w$]*)AstroComponent(?=\s*\})/;

export function isAstroComponentImportSource(source: string | undefined): source is string {
return !!source && source.endsWith('.astro');
export function isComponentImportSource(source: string | undefined): source is string {
return !!source && (source.endsWith('.astro') || source.endsWith('.svelte') || source.endsWith('.vue'));
}

export function stripAstroComponentSuffix(name: string) {
Expand All @@ -20,11 +20,11 @@ export function stripAstroComponentSuffix(name: string) {
return name.slice(0, -ASTRO_COMPONENT_SUFFIX.length);
}

export function rewriteAstroImportText(text: string) {
export function rewriteComponentImportText(text: string) {
return text
.split('\n')
.map((line) => {
if (!ASTRO_IMPORT_FROM_PATTERN.test(line)) {
if (!COMPONENT_IMPORT_FROM_PATTERN.test(line)) {
return line;
}

Expand All @@ -35,7 +35,7 @@ export function rewriteAstroImportText(text: string) {
.join('\n');
}

export function getAlreadyImportedAstroComponentSources(
export function getAlreadyImportedComponentSources(
ts: typeof import('typescript'),
documentText: string,
) {
Expand All @@ -55,7 +55,7 @@ export function getAlreadyImportedAstroComponentSources(

const source = statement.moduleSpecifier.text;
const importClause = statement.importClause;
if (!importClause || importClause.isTypeOnly || !isAstroComponentImportSource(source)) {
if (!importClause || importClause.isTypeOnly || !isComponentImportSource(source)) {
continue;
}

Expand Down Expand Up @@ -108,7 +108,7 @@ export function mapEdit(edit: TextEdit, code: AstroVirtualCode, languageId: stri
}
}

edit.newText = rewriteAstroImportText(edit.newText);
edit.newText = rewriteComponentImportText(edit.newText);

return edit;
}
38 changes: 19 additions & 19 deletions packages/language-tools/language-server/test/units/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { addAstroTypes } from '../../dist/core/index.js';
import { getAstroMetadata } from '../../dist/core/parseAstro.js';
import { patchTSX } from '../../dist/core/utils.js';
import {
getAlreadyImportedAstroComponentSources,
rewriteAstroImportText,
getAlreadyImportedComponentSources,
rewriteComponentImportText,
} from '../../dist/plugins/typescript/utils.js';
import * as utils from '../../dist/plugins/utils.js';

Expand Down Expand Up @@ -127,54 +127,54 @@ describe('Utilities', async () => {
});
});

it('rewriteAstroImportText - strips AstroComponent suffixes from default Astro imports', () => {
it('rewriteComponentImportText - strips AstroComponent suffixes from component imports', () => {
assert.strictEqual(
rewriteAstroImportText(`import ImageAstroComponent from "../components/Image.astro";\n`),
`import Image from "../components/Image.astro";\n`,
rewriteComponentImportText(`import ImageAstroComponent from "../components/Image.astro";\nimport CardAstroComponent from "../components/Card.vue";\nimport ChipAstroComponent from "../components/Chip.svelte";\n`),
`import Image from "../components/Image.astro";\nimport Card from "../components/Card.vue";\nimport Chip from "../components/Chip.svelte";\n`,
);
});

it('rewriteAstroImportText - only rewrites Astro imports', () => {
it('rewriteComponentImportText - only rewrites component imports', () => {
assert.strictEqual(
rewriteAstroImportText(`import ImageAstroComponent from "astro:assets";\n`),
rewriteComponentImportText(`import ImageAstroComponent from "astro:assets";\n`),
`import ImageAstroComponent from "astro:assets";\n`,
);
});

it('rewriteAstroImportText - preserves named imports on Astro component imports', () => {
it('rewriteComponentImportText - preserves named imports on component imports', () => {
assert.strictEqual(
rewriteAstroImportText(
rewriteComponentImportText(
`import ImageAstroComponent, { type Props } from "../components/Image.astro";\n`,
),
`import Image, { type Props } from "../components/Image.astro";\n`,
);
});

it('rewriteAstroImportText - strips AstroComponent suffixes from default aliases', () => {
it('rewriteComponentImportText - strips AstroComponent suffixes from default aliases', () => {
assert.strictEqual(
rewriteAstroImportText(
rewriteComponentImportText(
`import { default as ImageAstroComponent } from "../components/Image.astro";\n`,
),
`import { default as Image } from "../components/Image.astro";\n`,
);
});

it('getAlreadyImportedAstroComponentSources - detects runtime Astro component imports', () => {
it('getAlreadyImportedComponentSources - detects runtime component imports', () => {
assert.deepStrictEqual(
Array.from(
getAlreadyImportedAstroComponentSources(
getAlreadyImportedComponentSources(
ts,
`import Image from "../components/Image.astro";\nimport { default as Card } from "../components/Card.astro";\n`,
`import Image from "../components/Image.astro";\nimport { default as Card } from "../components/Card.vue";\nimport ChipComp from "../components/Chip.svelte";\n`,
),
),
['../components/Image.astro', '../components/Card.astro'],
['../components/Image.astro', '../components/Card.vue', '../components/Chip.svelte'],
);
});

it('getAlreadyImportedAstroComponentSources - ignores type-only Astro imports', () => {
it('getAlreadyImportedComponentSources - ignores type-only Astro imports', () => {
assert.deepStrictEqual(
Array.from(
getAlreadyImportedAstroComponentSources(
getAlreadyImportedComponentSources(
ts,
`import type { Props } from "../components/Image.astro";\n`,
),
Expand All @@ -183,10 +183,10 @@ describe('Utilities', async () => {
);
});

it('getAlreadyImportedAstroComponentSources - parses Astro frontmatter imports', () => {
it('getAlreadyImportedComponentSources - parses Astro frontmatter imports', () => {
assert.deepStrictEqual(
Array.from(
getAlreadyImportedAstroComponentSources(
getAlreadyImportedComponentSources(
ts,
`---
import Image from "../components/Image.astro";
Expand Down
Loading