Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/component-auto-import-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@marko/language-tools": patch
---

Name the default export of auto-discoverable Marko components (files under a `components`/`tags` directory) after the tag, so auto-imports read `import MyButton from "..."` instead of TypeScript's file-derived `MyButtonMarko` (and `Foo` instead of `index` for `foo/index.marko`).
154 changes: 154 additions & 0 deletions packages/language-server/src/__tests__/component-auto-import.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import assert from "node:assert/strict";

import { Project } from "@marko/language-tools";
import path from "path";
import { CancellationToken } from "vscode-languageserver";
import { URI } from "vscode-uri";

import MarkoLanguageService, { documents } from "../service";
import * as workspace from "../utils/workspace";

Project.setDefaultTypePaths({
internalTypesFile:
require.resolve("@marko/language-tools/marko.internal.d.ts"),
markoTypesFile: require.resolve("marko/index.d.ts"),
});

// `doComplete` reads editor config through the workspace connection; a stub that
// resolves every section to `{}` keeps the default preferences (auto-imports on).
workspace.setup({
onDidChangeConfiguration() {},
workspace: { getConfiguration: async () => ({}) },
} as never);

// A fresh virtual directory per call avoids stale parse/project caches; it sits
// under `fixtures` so taglib/tsconfig resolution works, but nothing is written
// to disk.
let runId = 0;
function openAll(components: Record<string, string>) {
const dir = path.join(
__dirname,
"fixtures",
"script",
`__component-${runId++}`,
);
const uris: Record<string, string> = {};
const opened: string[] = [];
for (const [rel, text] of Object.entries(components)) {
const uri = URI.file(path.join(dir, rel)).toString();
documents.doOpen({
textDocument: { uri, languageId: "marko", version: 1, text },
});
uris[rel] = uri;
opened.push(uri);
}
return {
uris,
dispose: () =>
opened.forEach((uri) => documents.doClose({ textDocument: { uri } })),
};
}

// Returns the default-export-related lines of a component's extracted module.
async function defaultExport(filename: string, text = "<div/>\n") {
const { uris, dispose } = openAll({ [filename]: text });
try {
const out = (await MarkoLanguageService.commands["$/showScriptOutput"](
uris[filename],
)) as { content: string } | undefined;
return (out?.content ?? "")
.split("\n")
.filter((line) => /export default|= new \(/.test(line))
.join("\n");
} finally {
dispose();
}
}

describe("marko component default export naming", () => {
it("names the export for a discoverable component file", async () => {
const out = await defaultExport("components/my-component.marko");
assert.match(out, /const MyComponent = new \(/);
assert.match(out, /export default MyComponent;/);
});

it("uses the directory name for index / self-named tag files", async () => {
assert.match(
await defaultExport("components/data-table/index.marko"),
/const DataTable = new \(/,
);
assert.match(
await defaultExport("tags/widget/widget.marko"),
/const Widget = new \(/,
);
});

it("leaves non-discoverable files as an anonymous export", async () => {
// Not under a `components`/`tags` directory.
assert.match(
await defaultExport("src/Modal.marko"),
/export default new \(/,
);
// A nested file that isn't the index or the self-named tag file.
const nested = await defaultExport("components/foo/bar.marko");
assert.match(nested, /export default new \(/);
assert.doesNotMatch(nested, /const \w+ = new \(/);
});

it("falls back to an anonymous export when the name appears in the source", async () => {
const out = await defaultExport(
"components/widget.marko",
"static const Widget = 1\n<div/>\n",
);
assert.match(out, /export default new \(/);
assert.doesNotMatch(out, /const Widget = new \(/);
});

it("skips naming when the tag name is not a valid identifier", async () => {
assert.match(
await defaultExport("components/3d-view.marko"),
/export default new \(/,
);
});

it("offers a clean `import MyComponent` auto-import", async () => {
const { uris, dispose } = openAll({
"components/my-component.marko": "<div/>\n",
"index.marko": "static const a = MyComponent\n",
});
try {
const doc = documents.get(uris["index.marko"])!;
const text = "static const a = MyComponent\n";
const result = await MarkoLanguageService.doComplete(
doc,
{
textDocument: { uri: uris["index.marko"] },
position: doc.positionAt(text.indexOf("MyComponent") + 11),
context: { triggerKind: 1 },
} as never,
CancellationToken.None,
);
const items = Array.isArray(result) ? result : (result?.items ?? []);
const item = items.find(
(i) =>
i.label === "MyComponent" &&
/\.marko$/.test(
(i.data as { originalSource?: string })?.originalSource ?? "",
),
);
assert.ok(item, "expected a MyComponent auto-import");
const resolved = await MarkoLanguageService.doCompletionResolve(
JSON.parse(JSON.stringify(item)),
CancellationToken.None,
);
assert.equal(
(resolved?.additionalTextEdits ?? [])
.map((e) => e.newText.trim())
.join(""),
'import MyComponent from "./components/my-component.marko";',
);
} finally {
dispose();
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const TestTag = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -54,3 +54,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default TestTag;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const TestTag = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -56,3 +56,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default TestTag;
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface Input {
})();
const __marko_internal_api = "tags";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const MyMenu = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -68,3 +68,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default MyMenu;
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const List = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -55,3 +55,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default List;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface Input<T> {
})();
const __marko_internal_api = "tags";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const Foo = new (class Template extends Marko._.Template<{
render<T>(
input: Marko.TemplateInput<Input<T>>,
stream?: {
Expand Down Expand Up @@ -61,3 +61,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input<T>>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default Foo;
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface Input {
})();
const __marko_internal_api = "tags";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const MyLayout = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -78,3 +78,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default MyLayout;
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const List = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -57,3 +57,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default List;
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export class Component extends Marko.Component {}
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new /**
const Child = new /**
* @extends { Marko._.Template<{ render( input: Marko.TemplateInput<Input>, stream?: { write: (chunk: string) => void; end: (chunk?: string) => void; }, ): Marko.Out<Component>; render( input: Marko.TemplateInput<Input>, cb?: ( err: Error | null, result: Marko.RenderResult<Component>, ) => void, ): Marko.Out<Component>; renderSync( input: Marko.TemplateInput<Input>, ): Marko.RenderResult<Component>; renderToString(input: Marko.TemplateInput<Input>): string; stream( input: Marko.TemplateInput<Input>, ): ReadableStream<string> & NodeJS.ReadableStream; mount( input: Marko.TemplateInput<Input>, reference: Node, position?: "afterbegin" | "afterend" | "beforebegin" | "beforeend", ): Marko.MountedTemplate<typeof input>; api: typeof __marko_internal_api, _(): () => <__marko_internal_input extends unknown>(input: Marko.Directives & Input & Marko._.Relate<__marko_internal_input, Marko.Directives & Input>) => (Marko._.ReturnWithScope<__marko_internal_input, void>); }>}
*/
(class Template extends Marko._.Template {})();
export default Child;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const Child = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -56,3 +56,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default Child;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface Input {
})();
const __marko_internal_api = "tags";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const Child = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -49,3 +49,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default Child;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface Input {
})();
const __marko_internal_api = "tags";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const Child = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -49,3 +49,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default Child;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface Input {
})();
const __marko_internal_api = "tags";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const Child = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -49,3 +49,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default Child;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface Input {
})();
const __marko_internal_api = "tags";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const Child = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -49,3 +49,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default Child;
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const FancyButton = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -61,3 +61,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default FancyButton;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const FancyButton = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -53,3 +53,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default FancyButton;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const TestTag = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -54,3 +54,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default TestTag;
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export { type Component };
})();
const __marko_internal_api = "class";
export { __marko_internal_api as "~api" };
export default new (class Template extends Marko._.Template<{
const MyTable = new (class Template extends Marko._.Template<{
render(
input: Marko.TemplateInput<Input>,
stream?: {
Expand Down Expand Up @@ -55,3 +55,4 @@ export default new (class Template extends Marko._.Template<{
Marko._.Relate<__marko_internal_input, Marko.Directives & Input>,
) => Marko._.ReturnWithScope<__marko_internal_input, void>;
}> {})();
export default MyTable;
Loading