diff --git a/packages/go/src/components/function/function.test.tsx b/packages/go/src/components/function/function.test.tsx
index e76ac05b7..2a34c8aa9 100644
--- a/packages/go/src/components/function/function.test.tsx
+++ b/packages/go/src/components/function/function.test.tsx
@@ -42,6 +42,88 @@ it("applies camelCase naming policy when not exported", () => {
`);
});
+it("applies explicit public prop - public=true with lowercase name", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ func MyFunction() {}
+ `);
+});
+
+it("applies explicit public prop - public=false with uppercase name", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ func myFunction() {}
+ `);
+});
+
+it("preserves original case when public prop is not specified", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ func MixedCaseFunction() {}
+ `);
+});
+
+it("handles reserved words with public prop", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ func Func_() {}
+ `);
+});
+
+it("handles single character names with public prop", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ func X() {}
+ `);
+});
+
+it("handles empty string names with public prop", () => {
+ expect(() => {
+ render(
+
+
+ ,
+ );
+ }).toThrow();
+});
+
+it("handles special characters in names with public prop", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ func Function_name_with_underscores() {}
+ `);
+});
+
it("defines single-line params and return type", () => {
const params = [
{
diff --git a/packages/go/src/components/function/function.tsx b/packages/go/src/components/function/function.tsx
index 35cafda8a..865d83181 100644
--- a/packages/go/src/components/function/function.tsx
+++ b/packages/go/src/components/function/function.tsx
@@ -60,6 +60,9 @@ export interface FunctionProps {
*/
singleLine?: boolean;
+ /** Whether the function should be public (exported) or private (unexported) */
+ public?: boolean;
+
children?: Children;
}
@@ -73,6 +76,7 @@ export function FunctionDeclaration(props: FunctionProps) {
const functionSymbol = createFunctionSymbol(props.name, !!props.receiver, {
refkeys: props.refkey,
+ public: props.public,
});
// scope for function declaration
diff --git a/packages/go/src/components/interface/declaration.tsx b/packages/go/src/components/interface/declaration.tsx
index d43edaf2c..42aff4981 100644
--- a/packages/go/src/components/interface/declaration.tsx
+++ b/packages/go/src/components/interface/declaration.tsx
@@ -150,11 +150,14 @@ export interface InterfaceFunctionProps {
refkey?: Refkey;
/** Doc comment */
doc?: Children;
+ /** Whether function should be public (exported) or private (unexported) */
+ public?: boolean;
}
export function InterfaceFunction(props: InterfaceFunctionProps) {
const symbol = createInterfaceMemberSymbol(props.name, {
refkeys: props.refkey,
+ public: props.public,
});
const functionScope = createFunctionScope();
diff --git a/packages/go/src/components/interface/interface.test.tsx b/packages/go/src/components/interface/interface.test.tsx
index af133efa0..41fdb87e7 100644
--- a/packages/go/src/components/interface/interface.test.tsx
+++ b/packages/go/src/components/interface/interface.test.tsx
@@ -173,6 +173,70 @@ describe("naming", () => {
}
`);
});
+
+ it("applies explicit public prop - public=true with lowercase name for interface functions", () => {
+ expect(
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestInterface interface {
+ func MyFunc() string
+ }
+ `);
+ });
+
+ it("applies explicit public prop - public=false with uppercase name for interface functions", () => {
+ expect(
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestInterface interface {
+ func myFunc() string
+ }
+ `);
+ });
+
+ it("preserves original case when public prop is not specified for interface functions", () => {
+ expect(
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestInterface interface {
+ func mixedCaseFunc() string
+ }
+ `);
+ });
+
+ it("handles reserved words with public prop for interface functions", () => {
+ expect(
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestInterface interface {
+ func Func_() string
+ }
+ `);
+ });
});
describe("embedded", () => {
diff --git a/packages/go/src/components/struct/declaration.tsx b/packages/go/src/components/struct/declaration.tsx
index f93e5b5fa..86f2b78a7 100644
--- a/packages/go/src/components/struct/declaration.tsx
+++ b/packages/go/src/components/struct/declaration.tsx
@@ -144,11 +144,14 @@ export interface StructMemberProps {
tag?: string | Record;
/** Doc comment */
doc?: Children;
+ /** Whether member should be public (exported) or private (unexported) */
+ public?: boolean;
}
export function StructMember(props: StructMemberProps) {
const symbol = createStructMemberSymbol(props.name, {
refkeys: props.refkey,
+ public: props.public,
});
const tagString = computed(() => {
diff --git a/packages/go/src/components/struct/struct.test.tsx b/packages/go/src/components/struct/struct.test.tsx
index 70dcc47d8..bf740d188 100644
--- a/packages/go/src/components/struct/struct.test.tsx
+++ b/packages/go/src/components/struct/struct.test.tsx
@@ -52,6 +52,78 @@ it("specify doc comment", () => {
`);
});
+it("applies explicit public prop - public=true with lowercase name for struct members", () => {
+ expect(
+
+
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestStruct struct{
+ MyField string
+ }
+ `);
+});
+
+it("applies explicit public prop - public=false with uppercase name for struct members", () => {
+ expect(
+
+
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestStruct struct{
+ myField string
+ }
+ `);
+});
+
+it("preserves original case when public prop is not specified for struct members", () => {
+ expect(
+
+
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestStruct struct{
+ MixedCaseField string
+ }
+ `);
+});
+
+it("handles reserved words with public prop for struct members", () => {
+ expect(
+
+
+
+
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type TestStruct struct{
+ Type_ string
+ }
+ `);
+});
+
it("defines fields", () => {
expect(
diff --git a/packages/go/src/components/type/declaration.test.tsx b/packages/go/src/components/type/declaration.test.tsx
new file mode 100644
index 000000000..69046678c
--- /dev/null
+++ b/packages/go/src/components/type/declaration.test.tsx
@@ -0,0 +1,51 @@
+import { Children, expect, it } from "vitest";
+import { TestPackage } from "../../../test/utils.js";
+import { TypeDeclaration } from "./declaration.js";
+
+it("applies explicit public prop - public=true with lowercase name for types", () => {
+ expect(
+
+ string
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type MyType string
+ `);
+});
+
+it("applies explicit public prop - public=false with uppercase name for types", () => {
+ expect(
+
+ string
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type myType string
+ `);
+});
+
+it("preserves original case when public prop is not specified for types", () => {
+ expect(
+
+ string
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type MixedCaseType string
+ `);
+});
+
+it("handles reserved words with public prop for types", () => {
+ expect(
+
+ string
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ type Type_ string
+ `);
+});
\ No newline at end of file
diff --git a/packages/go/src/components/type/declaration.tsx b/packages/go/src/components/type/declaration.tsx
index 41b7ca62c..f63a5cf5a 100644
--- a/packages/go/src/components/type/declaration.tsx
+++ b/packages/go/src/components/type/declaration.tsx
@@ -56,6 +56,8 @@ export interface TypeDeclarationProps {
doc?: Children;
/** Whether the type is an alias */
alias?: boolean;
+ /** Whether the type should be public (exported) or private (unexported) */
+ public?: boolean;
/** Type expression */
children?: Children;
/** Type parameters */
@@ -69,6 +71,7 @@ export function TypeDeclaration(props: TypeDeclarationProps) {
if (!symbol) {
symbol = createTypeSymbol(props.name, "type", {
refkeys: props.refkey,
+ public: props.public,
// TODO: set aliasTarget when alias is true
});
diff --git a/packages/go/src/components/var/declaration.test.tsx b/packages/go/src/components/var/declaration.test.tsx
new file mode 100644
index 000000000..c6ba37a8d
--- /dev/null
+++ b/packages/go/src/components/var/declaration.test.tsx
@@ -0,0 +1,63 @@
+import { Children, expect, it } from "vitest";
+import { TestPackage } from "../../../test/utils.js";
+import { VariableDeclaration, VariableDeclarationGroup } from "./declaration.js";
+
+it("applies explicit public prop - public=true with lowercase name for variables", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ var MyVar string
+ `);
+});
+
+it("applies explicit public prop - public=false with uppercase name for variables", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ var myVar string
+ `);
+});
+
+it("applies explicit public prop - public=true with const for variables", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ const MyConst string
+ `);
+});
+
+it("preserves original case when public prop is not specified for variables", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ var MixedCaseVar string
+ `);
+});
+
+it("handles reserved words with public prop for variables", () => {
+ expect(
+
+
+ ,
+ ).toRenderTo(`
+ package alloy
+
+ var Type_ string
+ `);
+});
\ No newline at end of file
diff --git a/packages/go/src/components/var/declaration.tsx b/packages/go/src/components/var/declaration.tsx
index a72fcc219..cbfbd0433 100644
--- a/packages/go/src/components/var/declaration.tsx
+++ b/packages/go/src/components/var/declaration.tsx
@@ -29,6 +29,8 @@ export interface VariableDeclarationProps {
doc?: Children;
/** Whether this is a `const` declaration */
const?: boolean;
+ /** Whether variable should be public (exported) or private (unexported) */
+ public?: boolean;
/** Initializer expression */
children?: Children;
}
@@ -67,7 +69,7 @@ export function VariableDeclaration(props: VariableDeclarationProps) {
const declarationGroupContext = useContext(VariableDeclarationGroupContext);
const inDeclarationGroup = !!declarationGroupContext?.active;
const isConst = declarationGroupContext?.const ?? props.const;
- const symbol = createVariableSymbol(props.name, { refkeys: props.refkey });
+ const symbol = createVariableSymbol(props.name, { refkeys: props.refkey, public: props.public });
const content = memo(() => {
const keyword = isConst ? "const" : "var";
diff --git a/packages/go/src/name-policy.ts b/packages/go/src/name-policy.ts
index 7c83d3b99..24ddc5e29 100644
--- a/packages/go/src/name-policy.ts
+++ b/packages/go/src/name-policy.ts
@@ -1,4 +1,4 @@
-import { createNamePolicy, NamePolicy, useNamePolicy } from "@alloy-js/core";
+import { createNamePolicy, NamePolicy, NamePolicyGetter, useNamePolicy } from "@alloy-js/core";
export type GoElements =
| "parameter"
@@ -37,15 +37,38 @@ const GLOBAL_RESERVED_WORDS = new Set([
"var",
]);
+/**
+ * Applies public/private naming convention for Go symbols.
+ * Public symbols use PascalCase, private symbols use camelCase.
+ * @param name - The original name
+ * @param isPublic - Whether the symbol should be public (exported)
+ * @returns The properly formatted name
+ */
+function applyPublicPrivateNaming(name: string, isPublic: boolean): string {
+ if (isPublic) {
+ // Public symbols should be PascalCase
+ return name.charAt(0).toUpperCase() + name.slice(1);
+ } else {
+ // Private symbols should be camelCase
+ return name.charAt(0).toLowerCase() + name.slice(1);
+ }
+}
+
/**
* Ensures a valid Go identifier for the given element kind.
* @param name - The name to validate.
* @param element - The Go element kind.
+ * @param isPublic - Whether the symbol should be public (exported).
* @returns A Go-safe name.
*/
-function ensureNonReservedName(name: string, _element: GoElements): string {
+function ensureNonReservedName(name: string, _element: GoElements, isPublic?: boolean): string {
const suffix = "_";
+ // Apply public/private naming convention if public flag is explicitly set
+ if (isPublic !== undefined) {
+ name = applyPublicPrivateNaming(name, isPublic);
+ }
+
// Global reserved words always need handling
if (GLOBAL_RESERVED_WORDS.has(name)) {
return `${name}${suffix}`;
@@ -54,6 +77,19 @@ function ensureNonReservedName(name: string, _element: GoElements): string {
return name;
}
+/**
+ * Creates a name policy getter that captures public flag
+ * @param element - The Go element kind
+ * @param isPublic - Whether the symbol should be public (exported)
+ * @returns A NamePolicyGetter with public flag captured
+ */
+export function createGoNamePolicyGetterWithPublic(
+ element: GoElements,
+ isPublic?: boolean,
+): NamePolicyGetter {
+ return (name: string) => ensureNonReservedName(name, element, isPublic);
+}
+
export function createGoNamePolicy(): NamePolicy {
return createNamePolicy((name, element) => {
return ensureNonReservedName(name, element);
diff --git a/packages/go/src/symbols/factories.ts b/packages/go/src/symbols/factories.ts
index fb46b0fed..3cdc52da3 100644
--- a/packages/go/src/symbols/factories.ts
+++ b/packages/go/src/symbols/factories.ts
@@ -1,6 +1,6 @@
import { Namekey, NamePolicyGetter } from "@alloy-js/core";
import { join } from "pathe";
-import { GoElements, useGoNamePolicy } from "../name-policy.js";
+import { GoElements, useGoNamePolicy, createGoNamePolicyGetterWithPublic } from "../name-policy.js";
import { useGoScope, useNamedTypeScope } from "../scopes/contexts.js";
import { GoFunctionScope } from "../scopes/function.js";
import { GoLexicalScope } from "../scopes/lexical.js";
@@ -223,12 +223,16 @@ export function createAnonymousTypeSymbol(
);
}
-function withNamePolicy(
+function withNamePolicy(
options: T,
elementType: GoElements,
-): GoSymbolOptions {
+): T {
+ const publicFlag = options.public;
return {
...options,
- namePolicy: options.namePolicy ?? useGoNamePolicy().for(elementType),
+ namePolicy: options.namePolicy ?? (publicFlag !== undefined ?
+ createGoNamePolicyGetterWithPublic(elementType, publicFlag) :
+ useGoNamePolicy().for(elementType)
+ ),
};
}
diff --git a/packages/go/src/symbols/go.ts b/packages/go/src/symbols/go.ts
index 70168e6da..9058276c0 100644
--- a/packages/go/src/symbols/go.ts
+++ b/packages/go/src/symbols/go.ts
@@ -12,7 +12,10 @@ import { PackageSymbol } from "./package.js";
/**
* Options for creating a Go symbol.
*/
-export interface GoSymbolOptions extends OutputSymbolOptions {}
+export interface GoSymbolOptions extends OutputSymbolOptions {
+ /** Whether the symbol should be public (exported) or private (unexported) */
+ public?: boolean;
+}
export type GoSymbolKinds =
| "symbol"