diff --git a/.chronus/changes/block-closer-2025-8-4-19-4-17.md b/.chronus/changes/block-closer-2025-8-4-19-4-17.md new file mode 100644 index 00000000..dfc805b4 --- /dev/null +++ b/.chronus/changes/block-closer-2025-8-4-19-4-17.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@alloy-js/core" +--- + +The `` component no longer emits an empty line when the `closer` prop is `false. \ No newline at end of file diff --git a/.chronus/changes/block-closer-2025-8-4-19-6-12.md b/.chronus/changes/block-closer-2025-8-4-19-6-12.md new file mode 100644 index 00000000..fe2758e4 --- /dev/null +++ b/.chronus/changes/block-closer-2025-8-4-19-6-12.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@alloy-js/python" +--- + +The `` component no longer emits an empty line after it. Use an explicit `` or `` to recover the old formatting. \ No newline at end of file diff --git a/packages/core/src/components/Block.tsx b/packages/core/src/components/Block.tsx index 6fc6985b..8ac1c593 100644 --- a/packages/core/src/components/Block.tsx +++ b/packages/core/src/components/Block.tsx @@ -10,9 +10,10 @@ export interface BlockProps { opener?: string; /** - * The closing punctuation of the block. Defaults to "\}". + * The closing punctuation of the block. Defaults to "\}". When passed `false`, + * no closing punctuation will be added and there will be no trailing newline. */ - closer?: string; + closer?: string | boolean; /** * Whether the block starts on a new line. When true, a hardline is added @@ -41,7 +42,7 @@ export function Block(props: BlockProps) { 0} softline={childCount.value === 0} - trailingBreak + trailingBreak={props.closer !== false} > {props.children} diff --git a/packages/core/test/components/block.test.tsx b/packages/core/test/components/block.test.tsx index 45449a5c..38fca7b0 100644 --- a/packages/core/test/components/block.test.tsx +++ b/packages/core/test/components/block.test.tsx @@ -46,3 +46,34 @@ it("renders properly with newline and no children", () => { } `); }); + +it("takes a custom closer and opener", () => { + const template = ( + <> + + Contents!!! + + following content + + ); + expect(template).toRenderTo(` + [ + Contents!!! + ]following content + `); +}); + +it("takes a false closer", () => { + const template = ( + <> + + Contents!!! + + following content + + ); + expect(template).toRenderTo(` + ( + Contents!!!following content + `); +}); diff --git a/packages/python/src/components/PythonBlock.tsx b/packages/python/src/components/PythonBlock.tsx index d24a9a89..1cd2fbf1 100644 --- a/packages/python/src/components/PythonBlock.tsx +++ b/packages/python/src/components/PythonBlock.tsx @@ -24,7 +24,7 @@ export interface PythonBlockProps { */ export function PythonBlock(props: PythonBlockProps) { return ( - + {props.children} ); diff --git a/packages/python/src/components/SourceFile.tsx b/packages/python/src/components/SourceFile.tsx index 46f7794c..2e83ec3c 100644 --- a/packages/python/src/components/SourceFile.tsx +++ b/packages/python/src/components/SourceFile.tsx @@ -2,7 +2,6 @@ import { ComponentContext, SourceFile as CoreSourceFile, createNamedContext, - List, Scope, Show, SourceDirectoryContext, @@ -84,9 +83,7 @@ export function SourceFile(props: SourceFileProps) { - - {props.children} - + {props.children} ); diff --git a/packages/python/test/classdeclarations.test.tsx b/packages/python/test/classdeclarations.test.tsx index 543fe31d..bd5434e8 100644 --- a/packages/python/test/classdeclarations.test.tsx +++ b/packages/python/test/classdeclarations.test.tsx @@ -14,8 +14,6 @@ describe("Python Class", () => { expect(result).toRenderTo(d` class Foo: pass - - `); }); @@ -26,8 +24,6 @@ describe("Python Class", () => { expect(result).toRenderTo(d` class Foo: pass - - `); }); @@ -38,8 +34,6 @@ describe("Python Class", () => { expect(result).toRenderTo(d` class Bar: print('hi') - - `); }); @@ -57,14 +51,10 @@ describe("Python Class", () => { const expected = d` class Base1: pass - class Base2: pass - class Baz(Base1, Base2): pass - - `; expect(result).toRenderTo(expected); }); @@ -75,11 +65,9 @@ describe("Python Class", () => { print('hello') , ]); - expect(result).toRenderTo(d` + expect(result).toRenderTo(` class Qux(Base): print('hello') - - `); }); @@ -102,24 +90,18 @@ describe("Python Class", () => { const mod1Expected = d` class A: pass - - `; const mod2Expected = d` from mod1 import A class B(A): pass - - `; const mod3Expected = d` from folder.mod2 import B class C(B): pass - - `; assertFileContents(result, { "mod1.py": mod1Expected }); assertFileContents(result, { "folder/mod2.py": mod2Expected }); @@ -141,12 +123,9 @@ describe("Python Class", () => { const expected = d` class A: pass - class B: bar: A foo: str - - `; expect(result).toRenderTo(expected); }); @@ -184,10 +163,8 @@ describe("Python Class", () => { const expected = d` class A: foo: str - class B: foo: str - A.foo B.foo `; @@ -229,14 +206,11 @@ describe("Python Class - VariableDeclaration", () => { const expected = d` class Base: pass - class A: just_name = None name_and_type: number = None name_type_and_value: number = 12 class_based: Base = None - - `; expect(result).toRenderTo(expected); }); @@ -293,8 +267,6 @@ describe("Python Class - VariableDeclaration", () => { instance_prop = 42 def instance_method(self) -> int: pass - - `, }); }); @@ -351,14 +323,10 @@ describe("Python Class - FunctionDeclaration", () => { b: int = None def my_method(self, a: int, b: int) -> int: return a + b - def my_class_method(cls) -> int: pass - def my_standalone_function() -> int: pass - - MyClass.my_class_method MyClass.my_standalone_function `; diff --git a/packages/python/test/classinstantiations.test.tsx b/packages/python/test/classinstantiations.test.tsx index 0cf9aa25..a46cc370 100644 --- a/packages/python/test/classinstantiations.test.tsx +++ b/packages/python/test/classinstantiations.test.tsx @@ -24,7 +24,6 @@ it("declaration of class instance with variables", () => { const expected = d` class OneClass: pass - OneClass("A name", 42, True) `; expect(result).toRenderTo(expected); @@ -147,11 +146,9 @@ it("incorrect Class instantiation works", () => { const expected = d` MyClass( class NestedClass: - pass - , + pass, def my_func(): - pass - , + pass, x = None ) `; diff --git a/packages/python/test/enums.test.tsx b/packages/python/test/enums.test.tsx index 28bd3e75..026d526e 100644 --- a/packages/python/test/enums.test.tsx +++ b/packages/python/test/enums.test.tsx @@ -28,8 +28,6 @@ describe("Python Enum", () => { RED = 1 GREEN = 2 BLUE = 3 - - `; expect(result).toRenderTo(expected); }); @@ -56,8 +54,6 @@ describe("Python Enum", () => { RED = "1" GREEN = 2 BLUE = "3" - - `; expect(result).toRenderTo(expected); }); @@ -85,15 +81,11 @@ describe("Python Enum", () => { class Dog: pass - class Cat: pass - class Animal(Enum): DOG = Dog CAT = Cat - - `; expect(result).toRenderTo(expected); }); @@ -117,8 +109,6 @@ describe("Python Enum", () => { DOG = auto() CAT = auto() RABBIT = auto() - - `; expect(result).toRenderTo(expected); }); @@ -148,8 +138,6 @@ describe("Python Enum", () => { READ = 1 WRITE = auto() EXECUTE = auto() - - `; expect(result).toRenderTo(expected); }); diff --git a/packages/python/test/externals.test.tsx b/packages/python/test/externals.test.tsx index a67c36de..f788c25a 100644 --- a/packages/python/test/externals.test.tsx +++ b/packages/python/test/externals.test.tsx @@ -98,12 +98,9 @@ it("uses import from external library in multiple functions", () => { response = get(1) return response.json() - def create_user(user_name: string) -> Response: response = post(1) return response.json() - - `; expect(result).toRenderTo(expected); }); @@ -177,13 +174,9 @@ it("uses import from external library in multiple class methods", () => { def get_user(self, user_id: int) -> Response: response = get(self.some_var) return response.json() - def create_user(self, user_name: string) -> Response: response = post(self.some_var) return response.json() - - - `; expect(result).toRenderTo(expected); }); diff --git a/packages/python/test/functioncallexpressions.test.tsx b/packages/python/test/functioncallexpressions.test.tsx index 531668f3..ffbea6b0 100644 --- a/packages/python/test/functioncallexpressions.test.tsx +++ b/packages/python/test/functioncallexpressions.test.tsx @@ -39,7 +39,6 @@ describe("FunctionCallExpression", () => { const expected = d` def run_func(): pass - run_func("A name", 42, True) `; expect(result).toRenderTo(expected); @@ -79,7 +78,6 @@ describe("FunctionCallExpression", () => { const expected = d` def run_func(name: str, number: int, flag: bool) -> str: pass - result: str = run_func("A name", 42, True) `; expect(result).toRenderTo(expected); diff --git a/packages/python/test/functiondeclaration.test.tsx b/packages/python/test/functiondeclaration.test.tsx index 82b3c900..e641653d 100644 --- a/packages/python/test/functiondeclaration.test.tsx +++ b/packages/python/test/functiondeclaration.test.tsx @@ -1,4 +1,4 @@ -import { namekey, refkey } from "@alloy-js/core"; +import { List, namekey, refkey } from "@alloy-js/core"; import { d } from "@alloy-js/core/testing"; import { describe, expect, it } from "vitest"; import * as py from "../src/index.js"; @@ -14,8 +14,6 @@ describe("Function Declaration", () => { expect(result).toRenderTo(d` def foo(): pass - - `); }); @@ -26,8 +24,6 @@ describe("Function Declaration", () => { expect(result).toRenderTo(d` def foo_bar(): pass - - `); }); @@ -38,8 +34,6 @@ describe("Function Declaration", () => { expect(result).toRenderTo(d` def foo() -> int: pass - - `); }); @@ -66,11 +60,8 @@ describe("Function Declaration", () => { expect(result).toRenderTo(d` def foo() -> int: pass - def bar() -> int: result: int = foo() - - `); }); @@ -86,9 +77,6 @@ describe("Function Declaration", () => { class MyClass: def bar(self): print('hi') - - - `); }); @@ -111,8 +99,6 @@ describe("Function Declaration", () => { d` def baz(x: int, y=0, z: int = 42, *args, **kwargs): print(x, y) - - `, ); }); @@ -127,9 +113,6 @@ describe("Function Declaration", () => { class MyClass: def __init__(self, x): pass - - - `); }); @@ -137,7 +120,6 @@ describe("Function Declaration", () => { expect(toSourceText([])).toBe(d` async def foo(): pass - `); }); @@ -149,7 +131,6 @@ describe("Function Declaration", () => { ).toBe(d` async def foo() -> Foo: pass - `); }); @@ -168,10 +149,8 @@ describe("Function Declaration", () => { ).toBe(d` class Foo: pass - async def foo() -> Foo: pass - `); }); @@ -185,7 +164,6 @@ describe("Function Declaration", () => { expect(toSourceText([decl])).toBe(d` def foo(a, b): return a + b - `); }); it("supports type parameters", () => { @@ -202,7 +180,6 @@ describe("Function Declaration", () => { expect(toSourceText([decl])).toBe(d` def foo[T, U](a, b): return a + b - `); }); it("renders function with parameters", () => { @@ -223,8 +200,6 @@ describe("Function Declaration", () => { class MyClass: def foo(self, x: int): self.attribute = "value" - - `); }); it("renders __init__ function with parameters", () => { @@ -241,8 +216,6 @@ describe("Function Declaration", () => { class MyClass: def __init__(self, x: int): self.attribute = "value" - - `); }); @@ -259,29 +232,37 @@ describe("Function Declaration", () => { parameters={parameters} refkey={fooRef} > - + - return z * 2 + + + return z * 2 + + <> + return{" "} + ]} + /> + + - return{" "} - ]} - /> - - return{" "} - ]} - /> + <> + return{" "} + ]} + /> + + ); @@ -292,7 +273,6 @@ describe("Function Declaration", () => { return z * 2 return foobar(2) return bar(3) - `); }); it("renders complex typing structure", () => { @@ -301,8 +281,10 @@ describe("Function Declaration", () => { , - - + + + + , { "mod1.py": ` class Foo: pass - `, "mod2.py": ` class A: pass - - + class B: pass - `, "usage.py": ` from mod1 import Foo @@ -341,7 +320,6 @@ describe("Function Declaration", () => { async def foo(x: A, y: B, *args, **kwargs) -> Foo: pass - `, }); }); diff --git a/packages/python/test/memberexpressions.test.tsx b/packages/python/test/memberexpressions.test.tsx index 6fe4af11..b9e8db7b 100644 --- a/packages/python/test/memberexpressions.test.tsx +++ b/packages/python/test/memberexpressions.test.tsx @@ -409,10 +409,8 @@ describe("with refkeys", () => { expect(toSourceText([template])).toBe(d` class Model1: foo: str - class Model2: bar: str - model1_instance: Model1 = Model1() model2_instance: Model2 = Model2() model1_instance.foo @@ -458,11 +456,9 @@ describe("with refkeys", () => { expect(toSourceText([template])).toBe(d` class Model: bar: str - def foo_function(foo: Model = None): message = foo.bar print(message) - `); }); @@ -518,13 +514,10 @@ describe("with refkeys", () => { ).toBe(d` class Bar: prop1: str - class Foo: test1: Bar = None def test_method() -> Bar: pass - - inst = Foo() inst.test1.prop1 inst.test_method() diff --git a/packages/python/test/namepolicies.test.tsx b/packages/python/test/namepolicies.test.tsx index 644caafd..51fa0600 100644 --- a/packages/python/test/namepolicies.test.tsx +++ b/packages/python/test/namepolicies.test.tsx @@ -11,8 +11,6 @@ it("correct formatting of class name", () => { const expected = d` class AReallyWeirdClassName: pass - - `; expect(result).toRenderTo(expected); }); @@ -55,8 +53,6 @@ it("renders a function with parameters", () => { d` def quirkly_named_function(a_parameter: int, *args, **kwargs): print(x, y) - - `, ); }); diff --git a/packages/python/test/pydocs.test.tsx b/packages/python/test/pydocs.test.tsx index 79fd42f0..15ded504 100644 --- a/packages/python/test/pydocs.test.tsx +++ b/packages/python/test/pydocs.test.tsx @@ -377,8 +377,6 @@ describe("Full example", () => { just_name = None name_and_type: number = None name_type_and_value: number = 12 - - `, ); }); @@ -469,8 +467,6 @@ describe("Full example", () => { just_name = None name_and_type: number = None name_type_and_value: number = 12 - - `, ); }); @@ -520,8 +516,6 @@ describe("Full example", () => { RED = 1 # The color red. GREEN = 2 # The color green. BLUE = 3 # The color blue. - - `; expect(result).toRenderTo(expected); }); diff --git a/packages/python/test/references.test.tsx b/packages/python/test/references.test.tsx index c5da5d9d..e730a71a 100644 --- a/packages/python/test/references.test.tsx +++ b/packages/python/test/references.test.tsx @@ -24,7 +24,6 @@ describe("Reference", () => { "models.py": ` class User: pass - `, "services.py": ` from models import User diff --git a/packages/python/test/sourcefiles.test.tsx b/packages/python/test/sourcefiles.test.tsx index 8e264a9a..170296b5 100644 --- a/packages/python/test/sourcefiles.test.tsx +++ b/packages/python/test/sourcefiles.test.tsx @@ -18,81 +18,83 @@ it("renders an empty source file", () => { it("correct formatting of source file", () => { const result = toSourceText([ - - - - - } - /> - } - /> - - - - - - - } - /> - - - } - /> - - } - /> - - , - - - } - /> - } - /> - - - - - - - } - /> - - , - - - - - , - - - - - , + + + + + + } + /> + } + /> + + + + + + + } + /> + + + } + /> + + } + /> + + + + + } + /> + } + /> + + + + + + + } + /> + + + + + + + + + + + + + , ]); const expected = d` class SomeClass: @@ -102,30 +104,20 @@ it("correct formatting of source file", () => { foo(a, b) a.b["special-prop"] z: int = 42 - some_var: int = 42 def some_other_method() -> str: pass - some_other_var: int = 42 - - def some_function(): x: int = 42 y: int = 42 foo(a, b) a.b["special-prop"] z: int = 42 - - class SomeOtherClass: def some_method() -> str: pass - - - a.b["special-prop"] - `; expect(result).toRenderTo(expected); }); diff --git a/packages/python/test/variables.test.tsx b/packages/python/test/variables.test.tsx index 67577272..c490f25d 100644 --- a/packages/python/test/variables.test.tsx +++ b/packages/python/test/variables.test.tsx @@ -148,7 +148,6 @@ describe("Python Variable", () => { expect(res).toBe(d` class MyClass: pass - my_var: MyClass = None`); }); @@ -169,7 +168,6 @@ describe("Python Variable", () => { "classes.py": ` class MyClass: pass - `, "usage.py": ` from classes import MyClass