Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 16 additions & 3 deletions rewrite-javascript/rewrite/src/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,30 @@ export abstract class Recipe {

async descriptor(): Promise<RecipeDescriptor> {
const optionsRecord: Record<string, OptionDescriptor> = (this as any).constructor[OPTIONS_KEY] || {}
// Java's `RecipeDescriptor.getXxx()` getters for collection-valued
// fields are treated as never-null by callers (matching what
// `Recipe.getDescriptor()` upholds locally). Always emit the
// collection keys, even when empty, so Jackson on the Java side
// never leaves them null.
return {
name: this.name,
displayName: this.displayName,
instanceName: this.instanceName(),
description: this.description,
tags: this.tags,
estimatedEffortPerOccurrence: this.estimatedEffortPerOccurrence,
recipeList: await mapAsync(await this.recipeList(), async r => r.descriptor()),
options: Object.entries(optionsRecord).map(([key, descriptor]) => ({
name: key,
value: (this as any)[key],
required: descriptor.required ?? true,
...descriptor
})),
dataTables: this.dataTables
preconditions: [],
recipeList: await mapAsync(await this.recipeList(), async r => r.descriptor()),
dataTables: this.dataTables,
maintainers: [],
contributors: [],
examples: []
}
}

Expand Down Expand Up @@ -135,9 +144,13 @@ export interface RecipeDescriptor {
readonly description: string
readonly tags: string[]
readonly estimatedEffortPerOccurrence: Minutes
readonly recipeList: RecipeDescriptor[]
readonly options: ({ name: string, value?: any } & OptionDescriptor)[]
readonly preconditions: RecipeDescriptor[]
readonly recipeList: RecipeDescriptor[]
readonly dataTables: DataTableDescriptor[]
readonly maintainers: any[]
readonly contributors: any[]
readonly examples: any[]
}

export interface OptionDescriptor {
Expand Down
6 changes: 5 additions & 1 deletion rewrite-javascript/rewrite/test/recipe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ describe("recipes", () => {
value: undefined
}
],
preconditions: [],
recipeList: [],
tags: [],
dataTables: []
dataTables: [],
maintainers: [],
contributors: [],
examples: []
});
});
});
14 changes: 12 additions & 2 deletions rewrite-python/rewrite/src/rewrite/rpc/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,13 @@ def _category_descriptor_to_dict(descriptor) -> dict:


def _recipe_descriptor_to_dict(descriptor) -> dict:
"""Convert a RecipeDescriptor to a dict for JSON serialization."""
"""Convert a RecipeDescriptor to a dict for JSON serialization.

Java's `RecipeDescriptor.getXxx()` getters for collection-valued fields
are treated as never-null by callers (matching what `Recipe.getDescriptor()`
upholds locally). Always emit the collection keys, even when empty, so
Jackson on the Java side never leaves them null.
"""
return {
'name': descriptor.name,
'displayName': descriptor.display_name,
Expand All @@ -897,8 +903,12 @@ def _recipe_descriptor_to_dict(descriptor) -> dict:
}
for name, value, opt in descriptor.options
],
'dataTables': descriptor.data_tables,
'preconditions': [],
'recipeList': [_recipe_descriptor_to_dict(r) for r in descriptor.recipe_list],
'dataTables': descriptor.data_tables,
'maintainers': [],
'contributors': [],
'examples': [],
}


Expand Down
27 changes: 27 additions & 0 deletions rewrite-python/rewrite/tests/rpc/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,30 @@ def fake_parse_python_source(source, path="<unknown>", relative_to=None, ty_clie
assert observed["source"] == ""
assert observed["path"] == str(tmp_path / "pkg" / "__init__.py")
assert (tmp_path / "pkg" / "__init__.py").read_text(encoding="utf-8") == ""


def test_recipe_descriptor_to_dict_emits_all_collection_keys():
"""Java's RecipeDescriptor.getXxx() collection-valued getters are
treated as never-null by callers. Always emit the collection keys —
including the ones the Python dataclass doesn't model — so Jackson on
the Java side never leaves them null."""
from rewrite.recipe import RecipeDescriptor
from rewrite.rpc.server import _recipe_descriptor_to_dict

descriptor = RecipeDescriptor(
name="org.example.Foo",
display_name="Foo",
description="A recipe.",
tags=[],
estimated_effort_per_occurrence=0,
options=[],
data_tables=[],
recipe_list=[],
)

result = _recipe_descriptor_to_dict(descriptor)

for key in ("tags", "options", "preconditions", "recipeList",
"dataTables", "maintainers", "contributors", "examples"):
assert key in result, f"missing key: {key}"
assert result[key] == [], f"{key} should be empty list, got {result[key]!r}"
Loading